Dependency injection-hilt

Dependency injection-hilt

[toc]

Reference

Jetpack's new member Hilt practice (1) Departure history

Comprehensive analysis of Hilt and Koin performance

A new member of Jetpack, an article to take you through Hilt and dependency injection

What is dependency injection

First look at the following sample code:

class Car { private val engine = Engine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() } Copy code

can be seen

Car
The class is created by itself
Engine
Object instance to execute
start
operating. This is a problem. If I want to get an ordinary car and an electric car, I have to create two classes.

Ordinary car:

class GasCar { private val engine = GasEngine() fun start() { engine.start() } } Copy code

electric car:

class ElectricCar { private val engine = ElectricEngine() fun start() { engine.start() } } Copy code

You can see that these two classes are only constructed

Engine
The implementation is different, but we have created two different classes, that is, the coupling of these two classes is too heavy. Let's optimize it:

class Car(private val engine: Engine) { fun start() { engine.start() } } fun main(args: Array) { val eEngine = ElectricEngine() val eCar = Car(eEngine) eCar.start() val gEngine = GasEngine() val gCar = Car(gEngine) gCar.start() } Copy code

After the optimization is complete

Engine
Give by parameter form
Car
Provide objects, so as to achieve the purpose of decoupling. This way of providing objects required by the calling class in the form of parameters is dependency injection.

Dependency injection is to give the caller what it needs. among them

rely
Refers to things that can be called by methods. In the form of dependency injection, the caller no longer directly obtains dependencies, but through
injection
Is passed to the caller. in
injection
After that, the caller will call the
rely
. Passing dependencies to the caller, instead of letting the caller obtain the dependencies directly, is the core of dependency injection.

Why use dependency injection

In summary, the biggest role of dependency injection is

Decoupling
. By understanding the concept of dependency injection, we know that through dependency injection, the caller does not have to directly produce the relying party. Both parties
Separation and decoupling
. And since the creation of the relying party object instance is completed by the dependency injection framework,
Reduced a lot of boilerplate code
,
Code is easier to understand
. Controlling the injected data through dependency injection allows
Testing is more convenient
. When the caller's life cycle is over, the dependency injection framework is okay
Automatically release the corresponding object to prevent memory usage
.

Dagger
,
Hilt
,
Koin
Both are dependency injection frameworks. among them
Hilt
is true
Dagger
The simple packaging makes the framework easier to use.
Koin
Is for
Kotlin
A lightweight dependency injection framework provided by developers. for
Hilt
with
Koin
For the comparison, you can read the following article:

Comprehensive analysis of Hilt and Koin performance

Let s first introduce

Hilt
How is it used.

Add dependencies

At the project root

build.gradle
Add the plug-in to the file:

buildscript { ... dependencies { ... classpath'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha' } } Copy code

in

app
And other uses
Hilt
Under the module
build.gradle
Add the following code to the file:

... apply plugin:'kotlin-kapt' apply plugin:'dagger.hilt.android.plugin' android { ... } dependencies { implementation "com.google.dagger:hilt-android:2.28-alpha" kapt "com.google.dagger:hilt-android-compiler:2.28-alpha" } Copy code

If you are using

Java
Development project, it is not necessary to introduce
kotlin-kapt
Plug-in, and
kapt
Replace keywords with
annotationProcessor
.

Finally, due to

Hilt
use
Java8
Function, so you need to
app/build.gradle
Add the following code to the file to enable
Java 8
Features:

android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } Copy code

Explanation of each annotation usage

@HiltAndroidApp

need to use

Hilt
Must be created
Application
Class and add
@HiltAndroidApp
annotation. followed by
Application
Register to
AndroidManifest.xml
File.

@HiltAndroidApp class SampleApplication: Application() Copy code
<application android:name=".SampleApplication"> </application> Copy code

@AndroidEntryPoint

Hilt
Currently supports injection
Android
There are 6 types of classes as follows:

  • Application
    (by using
    @HiltAndroidApp
    )
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

except

Application
need
@HiltAndroidApp
In addition to modification, other classes that require dependency injection need to be added
@AndroidEntryPoint
annotation.

@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { } Copy code

note:

  • Hilt
    Only supports extensions
    ComponentActivity
    of
    Activity
    ,Such as
    AppCompatActivity
    .
  • Hilt
    Only supports extensions
    androidx.Fragment
    of
    Fragment
    .
  • Hilt
    Does not support reserved
    Fragment
    .

@Inject

Then you can pass

@Inject
To perform the injection:

@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { @Inject lateinit var capBean: CapBean } Copy code

Note: by

Hilt
The injected field cannot be a private field.

It's not complete yet

capBean
Field injection, at this time
Hilt
Don't know how to create instance objects. Need to add on the constructor
@Inject
annotation. At this moment
Hilt
You know how to create the instance object that needs to be injected.

class CapBean @Inject constructor () to copy the code

Similarly, if you need to inject the object's constructor with parameters, you also need to add the required parameters to the constructor

@Inject
annotation.

class CapBean @Inject constructor(val waterBean: WaterBean) class WaterBean @Inject constructor() Copy code

@Module

In some cases, it is not possible to add

@Inject
Annotate the way to inform
Hilt
How to provide an instance of the class or interface, such as:

  • interface.
  • Classes from external libraries.

able to pass

@Module
Annotate to complete.
@Module
Annotations will be generated
Hilt
Module, provide concrete realization in the module.

@Binds

Inject the interface instance, you need to pass

@Module + @Binds
Annotation method.

First define an interface:

interface Water { fun drink() } Copy code

Then define the implementation class of the interface:

class Milk @Inject constructor(): Water { override fun drink() { Log.e("water", "drink milk") } } Copy code

Then define an abstract class to add

@Module
annotation:

@Module @InstallIn(ApplicationComponent::class) abstract class WaterModule { } Copy code

note:

@Module
Must and
@InstallIn
Share to inform
Hilt
The scope of each module.

Create a band in the abstract class

@Binds
Annotated abstract function:

@Module @InstallIn(ApplicationComponent::class) abstract class WaterModule { @Binds abstract fun bindsWater(milk: Milk): Water } Copy code

With

@Binds
The annotated function will
Hilt
Provide the following information:

  • The function return type will tell
    Hilt
    An instance of which interface the function provides.
  • The function parameters will tell
    Hilt
    Which implementation to provide.

to here

Hilt
You know how to inject the specific implementation of the interface.

@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { @Inject lateinit var water: Water } Copy code
@Provides

If a class is not owned by you, it cannot be injected through the constructor. able to pass

@Module + @Provides
Annotation tells
Hilt
How to provide an instance of this type.

First define a

@Module
Annotation-modified ordinary class, here is an ordinary class rather than an abstract class, because we need to provide the concrete implementation of the class instance. To
OkHttpClient
For example:

@Module @InstallIn(ActivityComponent::class) class NetworkModule { } Copy code

Create function add

@Provides
Annotation, provided by the function body
OkHttpClient
Specific creation of object instance:

@Module @InstallIn(ActivityComponent::class) class NetworkModule { @Provides fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .build() } } Copy code

With

@Provides
The annotated function will
Hilt
Provide the following information:

  • The function return type will tell
    Hilt
    Which type of instance the function provides.
  • The function parameters will tell
    Hilt
    Corresponding types of dependencies.
  • The function body will tell
    Hilt
    How to provide instances of the corresponding type. Whenever an instance of that type needs to be provided,
    Hilt
    The body of the function will be executed.

Then you can pass

Hilt
Injected:

@AndroidEntryPoint class MainActivity: AppCompatActivity() { @Inject lateinit var okHttpClient: OkHttpClient ... } Copy code

@Qualifier

On top we pass

@Binds
provided
Water
Interface
Milk
Realized and successfully injected into the class. If you need not only in the calling class
Milk
Realization needs another
Water
Implementation of the interface
Juice
. How to deal with it?

Add first

Juice
achieve:

class Juice @Inject constructor(): Water { override fun drink() { Log.e("water", "drink juice") } } Copy code

Still add to

@Module
In the module:

@Module @InstallIn(ActivityComponent::class) abstract class WaterModule { @Binds abstract fun bindsMilk(milk: Milk): Water @Binds abstract fun bindsJuice(juice: Juice): Water } Copy code

It is not possible to directly inject the calling class here:

@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { @Inject lateinit var juice: Water @Inject lateinit var milk: Water } Copy code

Hilt
There is no way to distinguish the two
Water
The instance is different, you need to
@Qualifier
To define annotations to distinguish different instances of the same type.

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MilkWater @Qualifier @Retention(AnnotationRetention.BINARY) annotation class MilkWater Copy code

Then add the two annotations defined to

WaterModule
In the function to tell
Hilt
How to provide different instances of the same instance:

@Module @InstallIn(ActivityComponent::class) abstract class WaterModule { @MilkWater @Binds abstract fun bindsMilk(milk: Milk): Water @JuiceWater @Binds abstract fun bindsJuice(juice: Juice): Water } Copy code

In the calling class, we also need to add the annotation we just defined to distinguish the difference between the two instances:

@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { @JuiceWater @Inject lateinit var juice: Water @MilkWater @Inject lateinit var milk: Water override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_hilt) juice.drink() milk.drink() } } Copy code

The output is as follows:

E/water: drink juice E/water: drink milk Copy code

@ApplicationContext, @ActivityContext

this is

Hilt
Some predefined qualifiers are provided.

  • @ApplicationContext
    :provide
    Application
    of
    Context
    .
  • @ActivityContext
    :provide
    Activity
    of
    Context
    .

Hilt components

Hilt
The role of the component is to
Hilt
The provided instance is injected into the corresponding
Android
class.
Hilt
The components are used as follows:

@Module @InstallIn(ActivityComponent::class) object MainModule { @Provides fun provideCoffeeBean(): CoffeeBean { return CoffeeBean() } } Copy code

due to

@InstallIn
The component set in the annotation is
ActivityComponent
, Which means
Hilt
Will pass
MainModule
for
Activity
Examples provided.

@Installin

@Installin
The annotations are as follows:

@Retention(CLASS) @Target({ElementType.TYPE}) @GeneratesRootInput public @interface InstallIn { Class<?>[] value(); } Copy code

@Installin
Contains a field, the available value of the field
Hilt
Provided components, these components represent the target to be injected
Android
class. The following table:

Hilt componentsObject to which the injector is oriented
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponentView annotated with @WithFragmentBindings
ServiceComponentService
Component life cycle

Hilt
Will follow accordingly
Android
The life cycle of the class automatically creates and destroys instances of the generated component class.

note:

ActivityRetainedComponent
It still exists after the configuration change, so it is called the first time
Activity#onCreate()
Created at the last call
Activity#onDestroy()
Destroyed at the time.

Component scope

Note: Modify the scope field needs to be deleted

build/generated/source/kapt
Re-run the next file.

by default,

Hilt
All the instances in are not set to a limited scope. In other words, every time a dependency is injected
Hilt
Will provide new examples. as follows:

class UserBean @Inject constructor () to copy the code
@AndroidEntryPoint class MainHiltActivity: AppCompatActivity() { @Inject lateinit var firstUserBean: UserBean @Inject lateinit var secondUserBean: UserBean override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_hilt) Log.e("hilt", firstUserBean.toString()) Log.e("hilt", secondUserBean.toString()) } } Copy code

The output is as follows:

E/hilt: com.sample.hilt.UserBean@b01decb E/hilt: com.sample.hilt.UserBean@dafe6a8 Copy code

By setting scope annotations, you can

Android
The same instance is shared in the class. as follows:

//Set the scope of UserBean to the `Activity` class @ActivityScoped class UserBean @Inject constructor() Copy code

The output is as follows:

E/hilt: com.sample.hilt.UserBean@b01decb E/hilt: com.sample.hilt.UserBean@b01decb Copy code

Hilt
Scope annotations and correspondence provided
Android
The relationship between the classes is as follows:

Android classGenerated componentsScope
ApplicationApplicationComponent@Singleton
View ModelActivityRetainedComponent@ActivityRetainedScope
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
View annotated with @WithFragmentBindingsViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped
Component hierarchy

@Module
The instance provided in a certain component level of the module can be used as a dependency in any sub-components under it. as follows:

class WaterBean class CapBean (val waterBean: WaterBean) Copy code
@Module @InstallIn(ActivityComponent::class) object HiltActivityModule { @Provides fun provideWaterBean(): WaterBean { return WaterBean() } } Copy code
@Module @InstallIn(FragmentComponent::class) object HiltFragmentModule { @Provides fun provideCapBean(waterBean: WaterBean): CapBean { return CapBean(waterBean) } } Copy code

Hilt
The hierarchical relationship of the components is as follows:

Default binding of components

Each

Hilt
Components are bound to a group by default
Android
class. such as
Activity
Component will inject all
Activity
Class, each
Activity
Class has
Activity
Different instances of components.

Inject dependencies in classes not supported by Hilt

Hilt
Supported
Android
There is no
ContentProvider
. This is mainly because
ContentProvider
Life cycle is special, it is in
Application
of
onCreate()
Method can be executed before, and
Hilt
Works from
Application
of
onCreate()
Start in the method, that is, before the method is executed,
Hilt
All of the functions of s are still not working properly.

If you want

ContentProvider
Used in
Hilt
To get some dependencies, you need to
ContentProvider
Define a type with
@EntryPoint
Annotated interface and pass
@InstallIn
The annotation declares its scope of action, as follows:

class MyContentProvider: ContentProvider() { @EntryPoint @InstallIn(ApplicationComponent::class) interface ExampleContentProviderEntryPoint { fun getWaterBean(): WaterBean } ... } Copy code

The function to obtain the instance is defined in the interface.

If you want to get the provided instance, you need to

EntryPointAccessors
Obtain as follows:

class MyContentProvider: ContentProvider() { ... override fun query(...): Cursor { context?.let { val entryPoint = EntryPointAccessors.fromApplication(it.applicationContext, MyEntryPoint::class.java) val waterBean = entryPoint.getWaterBean() } ... } } Copy code