Getting started guide | Jetpack Hilt dependency injection framework

Posted by jubripley on Tue, 15 Feb 2022 02:54:33 +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

    dependencies {
        //hilt
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
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"
class BaseApplication : Application() {

    override fun onCreate() {
        super.onCreate()
    }
}

The preparations will be finished here

Dependency injection using Hilt

    fun hiltTest() {
        Log.e("----------->", "hiltTest: ")
    }
}
class BaseApplication : Application() {

    @Inject
    lateinit var hiltTest: HiltTest

    override fun onCreate() {
        super.onCreate()
        hiltTest.hiltTest()
    }
}

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 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.
class HomeNavigationActivity : BaseLayoutActivity<TestViewModel>() {

    override fun setViewModel(): Class<TestViewModel> =TestViewModel::class.java

    override fun layout(): Int {
        return R.layout.home_navigation
    }

    override fun bindView() {
    }

}
@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()
    }
}

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.

  • @Module module is used to add binding to Hilt and tell 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.
@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()
    }

}

Use the following:

lateinit var hiltTest: HiltTest

@Inject
lateinit var hiltTest1: HiltTest

@Inject
lateinit var test1: Test

@Inject
lateinit var test2: Test

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

kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
  • Construct injection through @ ViewModelInject annotation.
  • SavedStateHandle uses @ Asssisted annotation
    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())
        }
    }
}
  • Inject through @ inject, and there is no need to manually create its object in viewModel
class HomeContentRepository @Inject constructor() : BaseRepository() {

    suspend fun requestBaidu(): String {
        return LvHttp.createApi(ApiServices::class.java).baidu()
    }
}
  • Get an instance of viewModel
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)
        })
}

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.

@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()
    }
}

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:

    @Inject
    lateinit var userDao: UserDao
}

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

    fun getName(): String
}
    override fun getName(): String {
        return "345"
    }
}

Then you need to create a new Module. Used to implement interface injection

@InstallIn(ApplicationComponent::class)
abstract class UserModule {
    @Binds
    abstract fun getUser(userImpl: UserImpl): User
}

Note: This Module is abstract.

Use the following:

class FragmentOne : BaseLayoutFragment<FragOneViewModel>() {

    @Inject
    lateinit var user: User
}

Use @ Qualifier to provide the same interface and different implementations

The User interface above has two different implementations, as follows:

    override fun getName(): String {
        return "345"
    }
}
    override fun getName(): String {
        return "Lv"
    }
}

Next, two annotations are defined

annotation class A

@Qualifier
annotation class B

Then modify the module to mark the corresponding dependencies in the 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
}

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:

class FragmentOne : BaseLayoutFragment<FragOneViewModel>() {

    @A
    @Inject
    lateinit var userA: User

    @B
    @Inject
    lateinit var userB: User
}

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. undefined

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