12

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

@Module
open class NetworkModule {

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

class DebugNetworkModule : NetworkModule() {

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

然后我在 Debug 构建中交换了正确的模块:

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

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

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

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

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

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

4

1 回答 1

12

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可选绑定

@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,以便在映射中没有绑定任何内容时,事情会在编译时而不是运行时失败

于 2020-06-20T12:55:29.463 回答