【问题标题】:Dagger Hilt provide alternative Modules for different flavors/build typesDagger Hilt 为不同的风格/构建类型提供替代模块
【发布时间】:2020-06-15 18:38:29
【问题描述】:

我尝试将应用迁移到Dagger Hilt。在我的旧设置中,我将模块切换为调试版本中的调试版本或不同的产品风格。例如:

@Module
open class NetworkModule {

    @Provides
    @Singleton
    open fun provideHttpClient(): OkHttpClient {
        ...
    }
}

class DebugNetworkModule : NetworkModule() {

    override fun provideHttpClient(): OkHttpClient {
        ...
    }
}

然后我在调试版本中换入了正确的模块:

val appComponent = DaggerAppComponent.builder().networkModule(DebugNetworkModule())

由于 Hilt 管理 ApplicationComponent,因此我认为不可能交换模块。

但是,当我查看生成的源代码(对我来说:DaggerApp_HiltComponents_ApplicationC)时,我发现 Hilt 确实为不同的模块生成了一个 Builder(除了 ApplicationContextModule 之外没有使用)。

知道这不是最佳做法。为每种构建类型/产品风格提供不同的NetworkModules 会更干净。但这会导致大量重复代码。

在测试中,我可以卸载模块并安装测试模块。但这在生产代码中似乎是不可能的。

还有其他方法可以实现我的目标吗?

【问题讨论】:

    标签: android dagger dagger-hilt


    【解决方案1】:

    Hilt 的关键在于,默认情况下,源代码中的模块 = 应用中安装的模块。

    选项 1:单独的代码路径

    理想情况下,您可以为不同的构建提供替代模块,并通过 sourceSets 区分使用哪些模块

    在发布源集中:

    @InstallIn(ApplicationComponent::class) 
    @Module
    object ReleaseModule {
      @Provides
      fun provideHttpClient(): OkHttpClient { /* Provide some OkHttpClient */ }
    }
    

    在调试源集中:

    @InstallIn(ApplicationComponent::class) 
    @Module
    object DebugModule {
      @Provides
      fun provideHttpClient(): OkHttpClient { /* Provide a different OkHttpClient */ }
    }
    

    选项 2:使用 @BindsOptionalOf 覆盖

    如果选项 1 不可行,因为您想覆盖源中仍然存在的模块,您可以使用 dagger optional binding

    @InstallIn(ApplicationComponent::class)
    @Module
    object Module {
      @Provides
      fun provideHttpClient(
        @DebugHttpClient debugOverride: Optional<OkHttpClient>
      ): OkHttpClient {
        return if (debugOverride.isPresent()) {
          debugOverride.get()
        } else {
          ...
        }
      }
    }
    
    @Qualifier annotation class DebugHttpClient
    
    @InstallIn(ApplicationComponent::class) 
    @Module
    abstract class DebugHttpClientModule {
      @BindsOptionalOf 
      @DebugHttpClient
      abstract fun bindOptionalDebugClient(): OkHttpClient
    }
    

    然后在一个文件中只在调试配置中:

    @InstallIn(ApplicationComponent::class) 
    @Module
    object DebugHttpClientModule {
      @Provides 
      @DebugHttpClient
      fun provideHttpClient(): OkHttpClient { ... }
    }
    

    选项 3:多重绑定@IntoMap

    如果您需要更多的粒度,而只是实现 + 测试/调试覆盖,您可以使用多重绑定和映射,使用键作为选择实现的优先级。

    @InstallIn(ApplicationComponent::class)
    @Module
    object Module {
      @Provides
      fun provideHttpClient(
        availableClients: Map<Int, @JvmSuppressWildcards OkHttpClient>
      ): OkHttpClient {
        // Choose the available client from the options provided.
        val bestEntry = availableClients.maxBy { it.key }
        return checkNotNull(bestEntry?.value) { "No OkHttpClients were provided" }
      }
    }
    

    主应用模块:

    @InstallIn(ApplicationComponent::class) 
    @Module
    object MainModule {
      @Provides 
      @IntoMap 
      @IntKey(0)
      fun provideDefaultHttpClient(): OkHttpClient {
        ...
      }
    }
    

    调试覆盖:

    @InstallIn(ApplicationComponent::class) 
    @Module
    object DebugModule {
      @Provides 
      @IntoMap 
      @IntKey(1)
      fun provideDebugHttpClient(): OkHttpClient {
        ...
      }
    }
    

    如果您使用选项 3,我要么将提供的类型设为可空/可选,要么避免使用 @Multibinds,这样如果映射中没有绑定任何内容,事情会在编译时而不是运行时失败

    【讨论】:

    • 在实际的生产代码中,有代码可以通过调试和测试调用并获取生产对象。例如,您可以像在生产中一样初始化 OkHttpClient,并在调试 Dagger 模块中添加 LoggingInterceptor。在那种情况下,我想知道如何使用 Dagger Hilt。 override fun provideHttpClient(): OkHttpClient { super.provideHttpClient().addNetworkInterceptor(HttpLoggingInterceptor()).build() }目前,我正在考虑自己准备一个工厂,并用调试等源集覆盖它。
    • 匕首多重绑定对您的情况有用吗? dagger.dev/dev-guide/multibindings.html 你可以绑定一组网络拦截器。您可以拥有仅存在于调试/测试代码中的提供程序@IntoSet,也可以拥有@ElementsIntoSet 的提供程序,该提供程序在 prod 中返回一个空集和一个包含项目的集合,具体取决于 BuildConfig.DEBUG 或其他一些运行时检查
    • 感谢您对建议做出反应。这种模式似乎适用于@IntoSet@ElementsIntoSet!但我需要以其他几种模式调用生产代码。例如,如果在“调试”菜单中启用了该选项,则返回 Fake 对象。 override fun provideApi() {if (fakeDebugEnabled) {FakeApi()} else {super.provideApi() }}。您可以包装其他模式,而不是返回 Fake 以打印调试信息或更改行为。那样的话就很难了。
    • 如果一个模块要在调试和非调试模式下使用,它必须允许以一种或另一种方式使用不同的调试选项的可能性,或者通过采用多个接口实现,Sets@IntoSet/@ElementsIntoSet 绑定的实现,或使用@BindsOptionalOfOptional 测试实现您还可以使用限定符对同一接口/类进行多个绑定,这可能对您的情况有所帮助
    猜你喜欢
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多