Getting started guide | Jetpack Hilt dependency injection framework

Posted by sonic_2k_uk on Sat, 12 Feb 2022 02:10:18 +0100

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!!