Jetpck Dagger-Hilt
What is dependency injection
There is a variable in a class, which is the dependency of the class. Then assign the value to this variable through external injection, which is called dependency injection.
What is Hilt
Hilt is Android's dependency injection and storage, which is actually based on Dagger. It can be said that hilt is specially built for Andorid.
Hilt creates a standard set of components and scopes. These components are automatically integrated into the life cycle of Android programs. When using, you can specify the scope of use, and things act in the corresponding life cycle.
Meaning of common annotations used by Hilt
- @HiltAndroidApp @HiltAndroidApp will trigger the code generation of Hilt as the base class of the program dependency container The generated Hilt is attached to the life cycle of the Application. It is the parent component of the App and provides dependencies for accessing other components After being configured in the Application, you can use the components provided by Hilt; Components include Application, Activity, Fragment, View, Service, etc.
- @HiltAndroidApp Create a dependency container that follows the Android lifecycle class. Currently, the supported types are: Activity, Fragment, View, Service, BroadcastReceiver
- @Inject Use @ Inject to tell Hilt how to provide instances of this class. It is often used in construction methods, non private fields, and methods. Hilt information about how to provide different types of instances is also called binding
- @Module module is used to provide some dependencies that cannot be used to construct @ Inject, such as the construction of third-party libraries, interfaces, build patterns, etc. For classes annotated with @ module, you need to use @ InstallIn annotation to specify the scope of module The class annotated with @ Module actually represents a Module and tells the container that binding can be used for installation through the specified component.
- @InstallIn For classes injected with @ module, you need to use the @ InstallIn annotation to specify the scope of the module. For example, a module annotated with @ InstallIn(ActivityComponent::class) will be bound to the life cycle of the activity.
- @Provides It is often used on the internal methods of the class marked by @ Module annotation. And provide dependency objects.
- @EntryPoint
Hilt supports the most common Android classes, such as Application, Activity, Fragment, View, Service, BroadcastReceiver, etc., but you may need to perform dependency injection in classes that hilt does not support. In this case, you can create them with @ EntryPoint annotation, and hilt will provide corresponding dependencies.
Component in Hilt
For classes using @ module annotation, you need to use @ Installin annotation to specify the scope of module.
For example, the Module annotated with @ InstallIn(ApplicationComponent::class) will be bound to the life cycle of the Application.
Hilt provides the following components to bind the activity scope that depends on the corresponding Android class
Hilt components | Scope of corresponding Android class activities |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
Hilt does not provide components for broadcast receivers because hilt injects broadcast receivers directly from ApplicationComponent.
Lifecycle of components in Hilt
Hilt will automatically create and destroy instances of components according to the corresponding Android class life cycle. The corresponding relationship is as follows:
Components provided by Hilt | Create corresponding lifecycle | End the corresponding life cycle | Scope of action |
---|---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() | @Singleton |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() | @ActivityRetainedScope |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() | @ActivityScoped |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() | @FragmentScoped |
ViewComponent | View#super() | View destroyed | @ViewScoped |
ViewWithFragmentComponent | View#super() | View destroyed | @ViewScoped |
ServiceComponent | Service#onCreate() | View destroyed | @ViewScoped |
How to use Hilt
buildscript { dependencies { //hilt classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha' } } Copy code
apply plugin: 'kotlin-kapt' apply plugin: 'com.xiaojinzi.component.plugin' //hilt api "com.google.dagger:hilt-android:2.28-alpha" kapt "com.google.dagger:hilt-android-compiler:2.28-alpha" Copy code
@HiltAndroidApp class BaseApplication : Application() { override fun onCreate() { super.onCreate() } } Copy code
The preparations will be finished here
Dependency injection using Hilt
class HiltTest @Inject constructor() { fun hiltTest() { Log.e("----------->", "hiltTest: ") } } Copy code
@HiltAndroidApp class BaseApplication : Application() { @Inject lateinit var hiltTest: HiltTest override fun onCreate() { super.onCreate() hiltTest.hiltTest() } } Copy code
Use of Hilt in Android components
- If @ AndroidEntryPoint is used to annotate the Android class, the Android class that depends on it must also be annotated;
- For example, after using @ AndroidEntryPoint for fragment, you also need to rely on @ AndroidEntryPoint for the Activity that fragment depends on, otherwise an exception will occur
- @AndroidEntryPoint cannot be written on an abstract class as
- @The AndroidEntryPoint annotation only supports subclasses of ComponentActivity, such as Fragment, AppCompatActivity, and so on.
@AndroidEntryPoint class HomeNavigationActivity : BaseLayoutActivity<TestViewModel>() { override fun setViewModel(): Class<TestViewModel> =TestViewModel::class.java override fun layout(): Int { return R.layout.home_navigation } override fun bindView() { } } Copy code
// For use in fragment, you need to add annotations to the activity it depends on @AndroidEntryPoint class FragmentOne : BaseLayoutFragment<FragOneViewModel>() { //Use @ Inject to get dependencies from components for injection @Inject lateinit var hiltTest: HiltTest override fun layout(): Int { return R.layout.frag_one } override fun bindView(rootView: View) { //The object has been injected and can be called directly one.text = hiltTest.hiltTest() } } Copy code
Use of Hilt and third party components
If you need to inject third-party dependencies into the project, you can use the @ Module annotation. Use @ Module to annotate a common class and create a third-party dependent object in it.
- @The Module module is used to add bindings to the Hilt and tell the Hilt if different types of instances are provided. The class using @ Module is equivalent to a Module, which is often used to create dependent objects (such as Okhttp, Retrofit, etc.).
- For the class using @ module, you need to use #InstallIn to specify the scope of this module, which will be bound to the life cycle of the corresponding Android class
- @Providers, which are often used to mark the internal methods of classes with @ Module annotation, and provide dependency objects.
//The corresponding life cycle is application @Module @InstallIn(ApplicationComponent::class) object TestModule { /** * Every time is a new instance */ @Provides fun bindHiltTest(): HiltTest { XLog.e("--------bindHiltTest----") return HiltTest() } /** * Reuse the same instance globally */ @Provides @Singleton fun bindSingTest(): Test { XLog.e("--------bindSingTest----") return Test() } } Copy code
Use the following:
@Inject lateinit var hiltTest: HiltTest @Inject lateinit var hiltTest1: HiltTest @Inject lateinit var test1: Test @Inject lateinit var test2: Test Copy code
bindSingTest will only be called once, @ SingLeton is equivalent to a SingLeton
Use of Hilt and ViewModel
Before using it, you need to use the app Add support for viewModel under build
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01' kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01' Copy code
- Construct injection through @ ViewModelInject annotation.
- SavedStateHandle uses @ Asssisted annotation
class HomeContentViewModel @ViewModelInject constructor( private val response: HomeContentRepository, @Assisted val state: SavedStateHandle ) : ViewModel() { private val liveData by lazy { MutableLiveData<String>() } val testLiveData: LiveData<String> by lazy { liveData } fun requestBaiDu() { launchVmHttp { liveData.postValue(response.requestBaidu()) } } } Copy code
- Inject through @ inject, and there is no need to manually create its object in viewModel
@ActivityScoped class HomeContentRepository @Inject constructor() : BaseRepository() { suspend fun requestBaidu(): String { return LvHttp.createApi(ApiServices::class.java).baidu() } } Copy code
- Get an instance of viewModel
@AndroidEntryPoint class HomeContentActivity : AppCompatActivity(){ //Generate an instance of ViewModel private val viewModel by viewModels<HomeContentViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home_content) viewModel.requestBaiDu() viewModel.testLiveData.observe(this, Observer { ToastUtils.show(it) }) } Copy code
Use of Hilt and Room
The @ Module annotation is needed here. The common class annotated with @ Module provides an instance of Room. And use @ InstallIn to declare the scope.
@Module @InstallIn(ApplicationComponent::class) object RoomModel { /** * @Provides: It is commonly used for internal methods of classes marked by @ Module and provides dependent objects * @Singleton: Provide single example */ @Provides @Singleton fun provideAppDataBase(application: Application): AppDataBase { return Room .databaseBuilder(application, AppDataBase::class.java, "knif.db") .fallbackToDestructiveMigration() .allowMainThreadQueries() .build() } @Provides @Singleton fun providerUserDao(appDataBase: AppDataBase): UserDao { return appDataBase.getUserDao() } } Copy code
We use @ Provides annotation and @ Singleton annotation for providerUserDao to tell Hilt that appdatabase needs to be executed when using UserDao getUserDao() .
While calling AppDataBase When getuserdao() needs to pass in AppDataBase, the above method provideAppDataBase will be called, because this method also uses the @ Provides annotation.
And both methods are singletons and will be called only once.
Use the following:
class FragmentTwo : BaseLayoutFragment<FragTwoViewModel>() { @Inject lateinit var userDao: UserDao } Copy code
Up to now, you can get UserDao anywhere without creating an instance manually.
Interface injection using @ bindings
Bindings: an abstract function must be annotated. The return value of the abstract function is the implemented interface. Specify the implementation by adding a unique parameter with an interface implementation type.
First, you need an interface and an implementation class
interface User { fun getName(): String } Copy code
class UserImpl @Inject constructor() : User { override fun getName(): String { return "345" } } Copy code
Then you need to create a new Module. Used to implement interface injection
@Module @InstallIn(ApplicationComponent::class) abstract class UserModule { @Binds abstract fun getUser(userImpl: UserImpl): User } Copy code
Note: This Module is abstract.
Use the following:
@AndroidEntryPoint class FragmentOne : BaseLayoutFragment<FragOneViewModel>() { @Inject lateinit var user: User } Copy code
Use @ Qualifier to provide the same interface and different implementations
The User interface above has two different implementations, as follows:
class UserAImpl @Inject constructor() : User { override fun getName(): String { return "345" } } Copy code
class UserBImpl @Inject constructor() : User { override fun getName(): String { return "Lv" } } Copy code
Next, two annotations are defined
@Qualifier annotation class A @Qualifier annotation class B Copy code
Then modify the module to mark the corresponding dependencies in the module.
@Module @InstallIn(ApplicationComponent::class) abstract class UserAModule { @A @Singleton @Binds abstract fun getUserA(userImpl: UserAImpl): User } @Module @InstallIn(ActivityComponent::class) abstract class UserBModule { @B @ActivityScoped @Binds abstract fun getUserB(userImpl: UserBImpl): User } Copy code
Here, two different mdule s are used and correspond to two different component s, one is application and the other is activity
Finally, use the following:
@AndroidEntryPoint class FragmentOne : BaseLayoutFragment<FragOneViewModel>() { @A @Inject lateinit var userA: User @B @Inject lateinit var userB: User } Copy code
Problems encountered
When using @ AndroidEntryPoint annotation. You need to use this annotation in both fragment and actvity.
However, if the activity and fragment are not in the same module, an error will be reported.
For component-based projects, this situation is more difficult....
Found some information:
- One of the main problems is that by discovering modules in Hilt, it is impossible to distinguish which modules belong to components in the application (if they have indeed used Hilt) or components in other libraries
- Another problem is that he complicates and confuses the pre built component hierarchy. Just like all activities in your library, it makes no sense to make the parent an ApplicationComponent because you don't put the component into the Application. Similarly, if a library contains only fragments and is hosted in the activity of the Application, you may encounter a similar situation. You want the library fragments to be independent. It doesn't make sense to just use the FragmentComponent object as an ActivityComponent.
Hilt benefits
- Reduce the starting cost of Android developers using dependency injection framework
- There is a set of standard components and scopes inside. After the scope is declared, this class can only be used in the specified scope. It also provides the management of declaration cycle, which will automatically release the objects that are not in use, reduce the excessive use of resources, and provide code reusability.
- Easy to use, bid farewell to the cumbersome new... In this process, you only need to add annotations. The readability of the code is improved, the construction is simple, the coupling becomes low, and it is easy to test
- I think the biggest advantage is to manage their life cycle and can only be used in the corresponding scope. I feel very good.
Reference from: New Jetpack member Hit dependency injection framework Getting Started Guide Official documents If you have any questions, please point out, thank you!!