使用Android中的HILT使用依赖注入来清理代码
#kotlin #android #mobile #tristan

目录

  1. What we are doing
  2. The mental model
  3. The Application class
  4. The Hilt components
  5. Hilt bindings
  6. Hilt modules
  7. @Binds
  8. @Provides
  9. Scoping
  10. Resources

代码

简介

  • 我已经开始了我的下一个应用程序,这是一个Twitch客户端应用。这个系列将是我创建此应用时所面临的所有笔记和问题。

入门

  • 我不会花任何时间来设置刀柄的依赖项。您可以在文档中找到HERE

我们在做什么

  • 通过用刀柄注入依赖的力量,我们将从这种情况下采取代码:
class DataStoreViewModel(
    application:Application,
):AndroidViewModel(application) {

    private val tokenDataStore:TokenDataStore = TokenDataStore(application)
    val twitchRepoImpl: TwitchRepo = TwitchRepoImpl()

}

  • 对此:
@HiltViewModel
class DataStoreViewModel @Inject constructor(
    private val twitchRepoImpl: TwitchRepo,
    private val tokenDataStore:TokenDataStore
): ViewModel() {
}

  • 我们的代码不仅看起来更干净,而且还可以更容易测试。

心理模型

  • 如果您不熟悉依赖性注射,那么可能很难真正掌握Hilt在做什么。这就是为什么我喜欢我们这个心理模型:

diagram of Hilt dependency injection

  • 本质上是剑柄会创建components,任何时候我们的代码需要依赖性,我们都可以告诉它是从刀柄组件中获得该依赖性的。

应用程序类

  • 假设我们俩现在都有适当的依赖关系,下一步是用@HiltAndroidApp注释类。正如documentation中所述:

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

  • 我们可以这样创建此类:
@HiltAndroidApp
class HiltApplication:Application() {

}

  • 只需确保在AndroidManifest.xml文件中声明它:
<application
        android:name=".di.HiltApplication"

Android入口点

  • 要允许将刀具注入我们的代码中,我们必须用特定的注释向我们的类注释。可以找到注释的完整列表。但是,由于我们想将代码注入ViewModel,我们需要注释用@HiltViewModel表示ViewModel3。

  • 重要:重要的是要注意,当您用刀柄注释注释班级时,您还必须注释所有依赖于它的类的类。这意味着,如果我们用@HiltViewModel注释ViewModel,则必须用@AndroidEntryPoint和周围的活动来注释片段。

组件

  • @HiltViewModel@AndroidEntryPoint注释我们的课程为每个注释的Android类生成一个单独的握力组件。这些组件将具有直接与他们注释的课程有关的生命周期。可以在HERE中找到解释生命周期的详细图
  • 因此,要创建一个族组件,我们只需应用注释:
@HiltViewModel
class DataStoreViewModel(
    application:Application,
):AndroidViewModel(application) {

    private val tokenDataStore:TokenDataStore = TokenDataStore(application)
    val twitchRepoImpl: TwitchRepo = TwitchRepoImpl()

}

Hilt Bindings

  • Binding是一个在documentation中使用的术语。因此,让我们定义它,我们可以将一个约束力视为一个对象,可以告知其应如何创建我们的依赖性。我们告诉Hilt通过将@Inject constructor添加到主构造函数中使用binding
  • 现在,我们可以将tokenDataStoretwitchRepoImpl移至主要构造函数:
@HiltViewModel
class DataStoreViewModel @Inject constructor(
    private val twitchRepoImpl: TwitchRepo,
    private val tokenDataStore:TokenDataStore
): ViewModel() {
}

  • 这看起来可能不错,但尚不正常,这是因为我们没有定义任何绑定来告诉刀片如何创建这些依赖性。

  • 根据您的需求,只需添加刀柄并添加@Inject constructor注释即可。尝试运行您的应用程序,看看它是否崩溃。如果您的应用程序崩溃,则需要创建更具体的绑定,我们可以通过Hilt modules

  • 进行此功能。

模块

  • 如前所述,Modules用于创建更具体的绑定,而Hilt将使用该绑定来创建我们的依赖性。要创建一个模块,我们需要创建一个新类并用2个特定注释(@Module@InstallIn:)注释它:
@Module //defines class as a module
@InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {

}
  • @InstallIn注释用于定义我们的模块安装的哪个刀片组件。然后,这些模块将提供有关如何创建绑定的信息。对于我们的ViewModelComponent::class表示,该模块将存储在ViewModel Component中。

注入接口实例@binds

  • 模块的全部要点是为剑柄提供适当的信息,以便可以创建绑定,然后将其用于创建适当的依赖关系实例。对于我的代码,我希望将刀具注入界面,TwitchRepo。我们可以将接口注入@Binds,就像这样:
@Module
@InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {

    @Binds
    abstract fun bindsTwitchRepo(
        twitchRepoImpl: TwitchRepoImpl
    ):TwitchRepo

}

  • 使用@Binds注释,我们创建一个抽象功能,并提供两个信息:

1)函数返回类型:这告诉刀片我们的函数提供了什么界面。

2)函数参数:这告诉HILT提供哪种实现。

  • 使用我们在模块中创建的这种新依赖性,我们告诉Hilt,任何时候viewModel需要一个TwitchRepo实例,它应该实例化TwitchRepoImpl并将其传递给我们的代码。请注意我如何说的That anytime a ViewModel,此依赖性仅在视图模式中可用
  • 现在我们拥有基础知识,我们可以变得更复杂

注入@provides 的实例

  • @Binds一起,我们还可以在模块内使用另一种称为@Provides的注释。通常,我们将使用提供的绑定注释,原因有两个。 1) we do not own the class we want Hilt to instantiate2) we want to provide more details to Hilt。最终,代码看起来像这样:
@Module
@InstallIn(SingletonComponent::class)
object SingletonModule {

    @Singleton
    @Provides
    fun providesTwitchClient(): TwitchClient {
        return Retrofit.Builder()
            .baseUrl("https://api.twitch.tv/helix/")
            .addConverterFactory(GsonConverterFactory.create())
            .build().create(TwitchClient::class.java)
    }

    @Singleton
    @Provides
    fun providesTokenDataStore(
        @ApplicationContext appContext: Context
    ): TokenDataStore {
        return TokenDataStore(appContext)
    }
}

    正如我们之前提到的,
  • @InstallIn(SingletonComponent::class)意味着所有这些依赖关系都将存储在SingletonComponent中。根据component hierarchy,这意味着我们的这些依赖项将适用于我们所有的代码。现在我们需要谈论如何范围

范围

  • 在文档和博客文章中,您会不断看到报价:By default, all bindings in Hilt are unscoped. This means that each time your app requests the binding, Hilt creates a new instance of the needed type.,因此即使在我们的SingletonModule中,每当抓地力创建一个实例TwitchClientTokenDataStore时,这都是一个新实例。这不是我们想要的。为了解决此问题,我们需要将依赖项范围范围范围范围范围,以@Singleton的注释完成。这告诉HILT仅创建TwitchClientTokenDataStore,然后重复使用相同的实例。

  • 值得指出的是,只有在您的代码功能(对于我的代码)功能时,才应使用@Singleton注释。我只想要一个Retrofit实例,而TokenDataStore是一个仅允许一个实例的Datastore

资源

结论

  • 感谢您抽出宝贵的时间阅读我的博客文章。如果您有任何疑问或疑虑,请在下面发表评论或在Twitter上与我联系。