Dagger Hilt 为不同的风格/构建类型提供替代模块

dip*_*dip 7 android dagger dagger-hilt

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

@Module
open class NetworkModule {

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

class DebugNetworkModule : NetworkModule() {

    override fun provideHttpClient(): OkHttpClient {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

val appComponent = DaggerAppComponent.builder().networkModule(DebugNetworkModule())
Run Code Online (Sandbox Code Playgroud)

由于 Hilt 管理着ApplicationComponent我看不到交换模块的可能性。

但是,当我查看生成的源代码(对我来说DaggerApp_HiltComponents_ApplicationC:)时,我看到 Hilt 确实为不同的模块生成了一个构建器(在 旁边未使用ApplicationContextModule)。

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

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

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

Ste*_*kel 7

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

选项 1:单独的代码路径

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

在发布源集中:

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

在调试源集中:

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

选项 2:覆盖使用 @BindsOptionalOf

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

@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
}
Run Code Online (Sandbox Code Playgroud)

然后仅在调试配置中的文件中:

@InstallIn(ApplicationComponent::class) 
@Module
object DebugHttpClientModule {
  @Provides 
  @DebugHttpClient
  fun provideHttpClient(): OkHttpClient { ... }
}
Run Code Online (Sandbox Code Playgroud)

选项 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" }
  }
}
Run Code Online (Sandbox Code Playgroud)

主要应用模块:

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

调试覆盖:

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

如果您使用选项 3,我要么将提供的类型设为可空/可选,要么避免使用,@Multibinds以便在没有绑定到映射中的情况下在编译时而不是运行时失败