Blog Detail

  • 击剑编排软件

    如何使用Hilt? (How to use Hilt?)

    Source: CodingWithMitch

    资料来源: CodingWithMitch

    Add to build.gradle:

    添加到build.gradle:

    classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

    Add to app/build.gradle:

    添加到app / build.gradle:

    apply plugin: 'kotlin-kapt'

    apply plugin: 'dagger.hilt.android.plugin'

    android {

    ...

    compileOptions {

    sourceCompatibility JavaVersion.VERSION_1_8

    targetCompatibility JavaVersion.VERSION_1_8

    }

    //adding also this even though it is not in docs

    kotlinOptions {

    jvmTarget = JavaVersion.VERSION_1_8.toString()

    }

    dependencies {

    implementation "com.google.dagger:hilt-android:2.28-alpha"

    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"

    }

    kapt {

    correctErrorTypes true

    }

    }

    All apps that use Hilt must contain an Application class that is annotated with @HiltAndroidApp , because it triggers Hilt’s code generation, including a base class for your application that serves as the application-level dependency container

    所有使用Hilt的应用程序都必须包含一个@HiltAndroidApp注释的Application类,因为它会触发Hilt的代码生成,包括用作应用程序级依赖关系容器的应用程序的基类。

    The application component is used to hold a reference to the app component. The dagger or dagger-android app component was used for keeping a reference to something that holds dependencies that will live for the lifetime of the application. Hilt resolve this with a single annotation.

    应用程序组件用于保存对应用程序组件的引用。 dagger或dagger-android应用程序组件用于保留对保留依赖项的引用,这些依赖项在应用程序的整个生命周期中都有效。 通过单个注释来解决此问题。

    @HiltAndroidAppclass MyApplication : Application() {

    Add following line to AndroidManifest.xml:

    将以下行添加到AndroidManifest.xml :

    HILT场注入和构造函数注入 (HILT Field Injection and Constructor Injection)

    Field injection is a simpler way to do things, not many limitations or required workarounds, but constructor injection it the better way whenever possible. The main reason is that with constructor injection you’re passing parameters through the constructor therefore when that object gets instantiated you know what it needs and this is really good for production code and for testing(when building mocks).

    字段注入是一种更简单的操作方式,没有很多限制或所需的解决方法,但构造函数注入是更好的方式。 主要原因是使用构造函数注入时,您正在通过构造函数传递参数,因此,当实例化该对象时,您便知道其需要什么,这对于生产代码和测试(在构建模拟时)确实非常有用。

    Hilt can provide dependencies to other Android classes that have the @AndroidEntryPoint annotation. If you annotate an Android class with @AndroidEntryPoint then you also must annotate Android classes that depend on it. For example, if you annotate a fragment, then you must also annotate any activities where you use that fragment. @AndroidEntryPoint generates an individual Hilt component for each Android class in your project. These components can receive dependencies from their respective parent classes.

    Hilt可以提供对具有@AndroidEntryPoint批注的其他Android类的依赖。 如果使用@AndroidEntryPoint注释Android类,则还必须注释依赖于它的Android类。 例如,如果注释一个片段,则还必须注释使用该片段的所有活动。 @AndroidEntryPoint为项目中的每个Android类生成一个单独的Hilt组件。 这些组件可以从其各自的父类接收依赖关系。

    Example of field injection:

    场注入示例:

    Let’s say we want to inject parameter of type SomeClass to our MainActivity:

    假设我们要向我们的MainActivity注入SomeClass类型的参数:

    class SomeClass@Inject constructor() { fun doAThing(): String { return "Look I did a thing" }}

    Now let’s insert this class like this:

    现在让我们像这样插入此类:

    @AndroidEntryPointclass MainActivity : AppComaptActivity() { //field injection @Inject lateinit var someClass: SomeClass

    Example of constructor injection:

    构造函数注入示例:

    class SomeOtherClass@Injectconstructor() { fun doSomeOtherThing(): String { return "Look I did some other thing!" }}

    Now let’s pass this class to SomeClass as a dependency:

    现在让我们将该类作为依赖项传递给SomeClass :

    class SomeClass@Inject constructor( private val someOtherClass: SomeOtherClass) { fun doAThing(): String { return "Look I did a thing" } fun doSomeOtherThing(): String { return someOtherClass.doSomeOtherThing() }}

    Behind the scenes, in the compile-time dagger is gonna create an instance of SomeOtherClass , then it is going to build that, then dagger will create an instance of SomeClass and pass the instance of SomeOtherClass

    在幕后,在编译时,匕首将创建SomeOtherClass的实例,然后将其构建,然后匕首将创建SomeClass的实例并传递SomeOtherClass的实例

    HILT的作用域 (Scoping with HILT)

    Scoping allows you to “preserve” the object instance and provide it as a “local singleton” for the duration of the scoped component.

    范围界定使您可以“保留”对象实例,并在范围化组件的持续时间内将其作为“本地单例”提供。

    You can use scope annotations to limit the lifetime of an object to the lifetime of its component. This means that the same instance of a dependency is used every time that type needs to be provided

    您可以使用范围注释将对象的生存期限制为其组件的生存期。 这意味着每次需要提供类型时,都使用相同的依赖项实例

    HILT generates all components necessary, like app component, activity component, fragment component. While it is generating all of these components it also generates scopes for all of these components, so for example the app component is automatically given the Singleton scope and this scope will live as long as the application is alive. So if dependency is annotated with Singleton it will exist as long as the application is alive.

    HILT生成所有必要的组件,例如应用程序组件,活动组件,片段组件。 在生成所有这些组件的同时,它还生成了所有这些组件的作用域 ,因此,例如,自动为应用程序组件赋予了Singleton范围 只要应用程序仍在运行,此作用域将一直存在。 因此,如果使用Singleton注释依赖项,则只要该应用程序处于活动状态,它将一直存在。

    Next, ActivityRetainedComponent(ViewModel) will receive ActivityRetainedScope. ViewModel will stay alive longer than Activity but it will die before application dies.

    接下来, ActivityRetainedComponent(ViewModel)将收到ActivityRetainedScope。 ViewModel的存活时间将比Activity更长,但是它将在应用程序终止之前终止。

    ActivityComponent will receive ActivityScope, and this scope will only live as an activity, so if activity dies, all the dependencies that are scoped with ActivityScope also die.

    ActivityComponent将接收ActivityScope ,并且该范围仅作为活动存在,因此,如果活动消失,则所有与ActivityScope一起作用域的依赖项也会消失。

    FragmentComponent will receive FragmentScope and so on.

    FragmentComponent将接收FragmentScope等。

    A tier system

    分层系统

    作用域示例(使用先前的示例) (Example of scoping (using previous examples))

    //annotating this class with @Singleton means that this dependecy //will live as long as the application is alive@Singletonclass SomeClass@Injectconstructor() { fun doAThing(): String { return "Look I did a thing!" }}

    Now let’s use FragmentScope:

    现在让我们使用FragmentScope:

    @AndroidEntryPoint //meaning that this class is user of dependencyclass MyFragment: Fragment() { @Inject lateinit var someClass: SomeClass}

    If we run the code, everything runs perfectly.

    如果我们运行代码,则一切运行正常。

    Dagger2 checks for error during the compile-time, which is better than to check for error during run-time, meaning that if there are errors in the code it will be clear when we try to build our project, not when the project is already running. Which is a huge advantage of Dagger over the KOIN (another DI)

    Dagger2在编译时检查错误,这比在运行时检查错误更好,这意味着如果代码中有错误,则在我们尝试构建项目时(而不是在已经存在项目时)将很清楚运行。 这是Dagger优于KOIN(另一个DI)的巨大优势

    If we swap @Singleton annotation of SomeClass with @ActivityScoped everything will still work fine.

    如果我们换@Singleton与SomeClass的注解@ActivityScoped一切都将仍然工作正常。

    But if we swap this annotation with @FragmentScoped and try to run the project, we will receive a compile-time error and the reason for that is that we are trying to inject something that is fragment scoped into Activity.

    但是,如果将这个批注与@FragmentScoped交换并尝试运行项目,则会收到编译时错误 ,其原因是我们试图将范围为片段的内容注入到Activity中。

    So this is a tear system that goes downwards meaning you cannot inject something that is fragment scoped into something that is above like @ActivityScoped

    因此,这是一个下降的催泪系统,这意味着您无法将范围为片段的对象注入到@ActivityScoped类的@ActivityScoped

    您无法进行承包商/现场注入的两种情况 (Two situations where you cannot do contractors/field injections)

    Let’s look at another example:

    让我们看另一个例子:

    @AndroidEntryPointclass

    MainActivity : AppCompatActivity() {

    @Inject

    lateinit var someClass: SomeClass

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    println(someClass.doAThing())

    }}

    class SomeClass

    @Inject

    constructor(

    private val someDependency: SomeDependency

    ){

    fun doAThing(): String{

    return "Look I got: ${someDependency.getAThing()}"

    }

    }

    class SomeDependency

    @Inject

    constructor(){

    fun getAThing() : String{

    return "A Thing"

    }

    }

    What if we now add an interface like this one:

    如果现在添加这样的接口,该怎么办:

    interface SomeInterface { fun getAThing() : String}

    And make SomeDependency class to implement it:

    并使SomeDependency类实现它:

    class SomeInterfaceImpl@Injectconstructor() : SomeInterface { override fun getAThing() : String{ return "A Thing" }}

    And let’s add one more change:

    让我们再添加一个更改:

    class SomeClass @Injectconstructor( private val someInterfaceImpl: SomeInterface){ fun doAThing(): String{ return "Look I got: ${someInterfaceImpl.getAThing()}" }}

    If we run this, we will get a compile-time error, and that’s because when we do constructor injection or field injection we cannot inject an interface.

    如果运行此命令,则会出现编译时错误,这是因为在进行构造函数注入或字段注入时,无法注入interface 。

    The second situation is very similar to the first one, and it happens when we want to insert something that we don’t own, like some third-party library:

    第二种情况与第一种情况非常相似,并且发生在我们想要插入不属于我们的东西(例如某些第三方库)时:

    class SomeClass @Injectconstructor( private val someInterfaceImpl: SomeInterface, private val gson: Gson){ fun doAThing(): String{ return "Look I got: ${someInterfaceImpl.getAThing()}" }}

    In both of these cases, dagger has no idea how to create these dependencies.

    在这两种情况下,dagger都不知道如何创建这些依赖项。

    解决方案是Hilt模块 (The solution is Hilt modules)

    Sometimes a type cannot be constructor-injected. This can happen for multiple reasons. For example, you cannot constructor-inject an interface. You also cannot constructor-inject a type that you do not own, such as a class from an external library. In these cases, you can provide Hilt with binding information by using Hilt modules.

    有时无法将构造函数注入类型。 发生这种情况有多种原因。 例如,您不能构造函数注入接口。 您也不能构造函数注入您不拥有的类型,例如来自外部库的类。 在这些情况下,可以通过使用Hilt模块为Hilt提供绑定信息。

    A Hilt module is a class that is annotated with @Module. Like a Dagger module, it informs Hilt how to provide instances of certain types. Unlike Dagger modules, you must annotate Hilt modules with @InstallIn to tell Hilt which Android class each module will be used or installed in. -from AndroidDocs

    Hilt模块是用@Module注释的类。 像Dagger模块一样 ,它通知Hilt如何提供某些类型的实例。 与Dagger模块不同,您必须使用@InstallIn注释Hilt模块,以告知Hilt每个模块将在哪个Android类中使用或安装。- from AndroidDocs

    To solve this problem we can use @Provides or @Binds , where @Provides is an easier option.

    为了解决这个问题,我们可以使用@Provides或@Binds ,其中@Provides是一个更简单的选项。

    First, let’s consider the more complex way a@Binds method that can't be used in all situations, to solve the first problem with interfaces. Let’s create a module to tell Hilt how to build the interface object :

    首先,让我们考虑不能在所有情况下使用的@Binds方法的更复杂方法,以解决接口的第一个问题。 让我们创建一个模块来告诉Hilt如何构建接口对象:

    @InstallIn(ApplicationComponent::class)//to install module into AppComponent@Moduleabstract class MyModule { @Singleton @Binds abstract fun bindSomeDependency( someImpl: SomeInterfaceImpl ): SomeInterface

    And remove SomeClass Gson parameter:

    并删除SomeClass Gson参数:

    class SomeClass @Injectconstructor( private val someInterfaceImpl: SomeInterface){ fun doAThing(): String{ return "Look I got: ${someInterfaceImpl.getAThing()}" }}

    If we run this code, it will work fine, because now Hilt knows how to create this parameter.

    如果我们运行此代码,它将正常工作,因为现在Hilt知道如何创建此参数。

    Interesting note, if we change ApplicationComponent to ActivityComponent we will get a compile-time error, because of the @SingletonScope which need to change to @ActivityScoped

    值得注意的一点是,如果我们改变ApplicationComponent到ActivityComponent我们会得到一个编译时错误,因为的@SingletonScope这就需要改变@ActivityScoped

    Now, the situation where @Binds does not work. Let’s add back our Gson parameter:

    现在, @Binds无法正常工作的情况。 让我们重新添加我们的Gson参数:

    class SomeClass @Injectconstructor( private val someInterfaceImpl: SomeInterface, private val gson: Gson){ fun doAThing(): String{ return "Look I got: ${someInterfaceImpl.getAThing()}" }}

    And if we now provide a function to provide this parameter:

    如果我们现在提供一个函数来提供此参数:

    @InstallIn(ApplicationComponent::class)

    @Module

    abstract class MyModule {

    @Singleton

    @Binds

    abstract fun bindSomeDependency(

    someImpl: SomeInterfaceImpl

    ): SomeInterface

    @ActivityScoped

    @Binds

    abstract fun bindGson(

    gson: Gson

    ):Gson

    }

    If we run this, we will receive a compile-time error, because @Binds can’t be used for this scenario.

    如果运行此命令,则会收到编译时错误,因为@Binds不能用于这种情况。

    @Provides method:

    @Provides方法:

    @InstallIn(ApplicationComponent::class)@Moduleclass MyModule { @Singleton @Provides fun provideSomeInterface(): SomeInterface { return SomeInterfaceImpl() }

    And in the case that there was a parameter in SomeInterfaceImpl class, like:

    并且在SomeInterfaceImpl类中有一个参数的情况下,例如:

    class SomeInterfaceImpl@Injectconstructor( private val someDependency: String) : SomeInterface { override fun getAThing() : String{ return "A Thing" }}

    Then we will add these lines in our module:

    然后,将这些行添加到模块中:

    @InstallIn(ApplicationComponent::class)

    @Module

    class MyModule {

    @Singleton

    @Provides

    fun provideSomeString(): String {

    return "some string"

    }

    @Singleton

    @Provides

    fun provideSomeInterface(

    someString: String

    ): SomeInterface {

    return SomeInterfaceImpl()

    }

    }

    Also, let’s resolve Gson parameter problem:

    另外,让我们解决Gson参数问题:

    @InstallIn(ApplicationComponent::class)

    @Module

    class MyModule {

    @Singleton

    @Provides

    fun provideSomeString(): String {

    return "some string"

    }

    @Singleton

    @Provides

    fun provideSomeInterface(

    someString: String

    ): SomeInterface {

    return SomeInterfaceImpl()

    }

    @Singleton

    @Provides

    fun provideGson(): Gson {

    return Gson()

    }

    }

    So then what’s a difference between @Binds and @Provides ?

    那么@Binds和@Provides什么@Provides ?

    @Binds generates more efficient code than @Provides because it never creates an implementation for that module. The method is also more concise. You can find furthermore comparison here.

    @Binds生成的代码比@Provides更为有效,因为它从未为该模块创建实现。 该方法也更加简洁。 您可以在这里找到更多的比较。

    如何提供相同类型的实例? (How to provide instances of the same type?)

    What if we are providing two string for the same module. How to tell Hilt which one to use when needed? The solution is Custom annotation.

    如果我们为同一模块提供两个字符串怎么办? 如何告诉Hilt在需要时使用哪一个? 解决方案是自定义注释 。

    Let’s say we have a couple of classes:

    假设我们有几个类:

    class SomeInterfaceImpl1

    @Inject

    constructor() : SomeInterface {

    override fun getAThing() : String{

    return "A Thing"

    }

    }

    class SomeInterfaceImpl2

    @Inject

    constructor() : SomeInterface {

    override fun getAThing() : String{

    return "A Thing"

    }

    }

    class SomeClass

    @Inject

    constructor(

    private val someInterfaceImpl1: SomeInterface,

    private val someInterfaceImpl2: SomeInterface,

    ){

    fun doAThing1(): String{

    return "Look I got: ${someInterfaceImpl1.getAThing()}"

    }

    fun doAThing2(): String{

    return "Look I got: ${someInterfaceImpl2.getAThing()}"

    }

    }

    Now let’s make changes so Hilt can differentiate between to interface implementations:

    现在让我们进行更改,以便Hilt可以区分接口实现:

    @InstallIn(ApplicationComponent::class)

    @Module

    class MyModule {

    @Impl1

    @Singleton

    @Provides

    fun provideSomeInterface(

    someString: String

    ): SomeInterface {

    return SomeInterfaceImpl1()

    }

    @Impl2

    @Singleton

    @Provides

    fun provideSomeInterface(

    someString: String

    ): SomeInterface {

    return SomeInterfaceImpl2()

    }

    }

    //outside of module class

    @Qualifier

    @Retention(AnnotationRetention.BINARY)

    annotation class Impl1

    @Qualifier

    @Retention(AnnotationRetention.BINARY)

    annotation class Impl2

    Now add these annotations in the constructor of SomeClass:

    现在,将这些注释添加到SomeClass的构造函数中:

    class SomeClass

    @Inject

    constructor(

    @Impl1 private val someInterfaceImpl1: SomeInterface,

    @Impl2 private val someInterfaceImpl2: SomeInterface,

    ){

    fun doAThing1(): String{

    return "Look I got: ${someInterfaceImpl1.getAThing()}"

    }

    fun doAThing2(): String{

    return "Look I got: ${someInterfaceImpl2.getAThing()}"

    }

    }

    Please check out my main source of information about this topic: CodingWithMitch

    请查看我有关此主题的主要信息源: CodingWithMitch