Hilt 循环依赖

Ski*_*ᴉʞS 7 android kotlin dagger-2 dagger-hilt

我正在使用 Hilt 创建一个宠物项目,也许我遇到这个问题是因为我正在安装所有内容SingletonComponent::class,也许我应该为每个项目创建组件。

pet 项目有一个NetworkModule, UserPrefsModule,当我尝试创建一个Authenticatorfor时,问题出现了OkHttp3

这是我的网络模块

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor()
        .apply {
            if (BuildConfig.DEBUG) level = HttpLoggingInterceptor.Level.BODY
        }

    @Singleton
    @Provides
    fun providesErrorInterceptor(): Interceptor {
        return ErrorInterceptor()
    }

    @Singleton
    @Provides
    fun providesAccessTokenAuthenticator(
        accessTokenRefreshDataSource: AccessTokenRefreshDataSource,
        userPrefsDataSource: UserPrefsDataSource,
    ): Authenticator = AccessTokenAuthenticator(
        accessTokenRefreshDataSource,
        userPrefsDataSource,
    )

    @Singleton
    @Provides
    fun providesOkHttpClient(
        httpLoggingInterceptor: HttpLoggingInterceptor,
        errorInterceptor: ErrorInterceptor,
        authenticator: Authenticator,
    ): OkHttpClient =
        OkHttpClient
            .Builder()
            .authenticator(authenticator)
            .addInterceptor(httpLoggingInterceptor)
            .addInterceptor(errorInterceptor)
            .build()

}
Run Code Online (Sandbox Code Playgroud)

那么我的UserPrefsModule是:

@Module
@InstallIn(SingletonComponent::class)
object UserPrefsModule {

    @Singleton
    @Provides
    fun provideSharedPreference(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
    }

    @Singleton
    @Provides
    fun provideUserPrefsDataSource(sharedPreferences: SharedPreferences): UserPrefsDataSource {
        return UserPrefsDataSourceImpl(sharedPreferences)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我有一个AuthenticatorModule

@Module
@InstallIn(SingletonComponent::class)
object AuthenticationModule {

    private const val BASE_URL = "http://10.0.2.2:8080/"

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create())
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .build()

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): AuthenticationService =
        retrofit.create(AuthenticationService::class.java)


    @Singleton
    @Provides
    fun providesAccessTokenRefreshDataSource(
        userPrefsDataSource: UserPrefsDataSource,
        authenticationService: AuthenticationService,
    ): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
        authenticationService, userPrefsDataSource
    )
}
Run Code Online (Sandbox Code Playgroud)

AccessTokenRefreshDataSourceImpl当我创建我需要的AuthenticationServiceand时,问题开始发生UserPrefsDataSource,并且我收到此错误:

错误:[Dagger/DependencyCycle] 发现依赖循环:公共抽象静态类 SingletonC 实现 App_GenerateInjector,

对于登录、登录、验证等每个功能。我正在创建一个新功能@Module,如下所示:

@Module
@InstallIn(SingletonComponent::class)
interface SignInModule {

    @Binds
    fun bindIsValidPasswordUseCase(
        isValidPasswordUseCaseImpl: IsValidPasswordUseCaseImpl,
    ): IsValidPasswordUseCase

    @Binds
    fun bindIsValidEmailUseCase(
        isValidEmailUseCase: IsValidEmailUseCaseImpl,
    ): IsValidEmailUseCase

     //Here in that Datasource I'm using the AuthenticationService from AuthenticationModule and it works
    @Binds
    fun bindSignInDataSource(
        signInDataSourceImpl: SignInDataSourceImpl
    ): SignInDataSource
}
Run Code Online (Sandbox Code Playgroud)

的构造函数AccessTokenAutenticator

class AccessTokenAuthenticator @Inject constructor(
    private val accessTokenRefreshDataSource: AccessTokenRefreshDataSource,
    private val userPrefsDataSource: UserPrefsDataSource,
) : Authenticator {
Run Code Online (Sandbox Code Playgroud)

的构造函数AccessTokenRefreshDatasource

class AccessTokenRefreshDataSourceImpl @Inject constructor(
    private val authenticationService: AuthenticationService,
    private val userPrefsDataSource: UserPrefsDataSource,
) : AccessTokenRefreshDataSource {
Run Code Online (Sandbox Code Playgroud)

请注意,我将所有内容都@Module按功能分开,以便将来能够模块化应用程序。

Jef*_*ica 15

在大多数编程语言中,如果您需要 B 的实例来构造 A,并需要 A 的实例来构造 B,那么您将无法构造其中任何一个。

这里:

  • AccessTokenRefreshDataSource 需要 AuthenticationService
  • AuthenticationService 需要 Retrofit
  • 改造需要OkHttpClient
  • OkHttpClient 需要 Authenticator
  • 身份验证器需要 AccessTokenRefreshDataSource

...因此,无论您的模块或组件结构如何,Dagger 都无法首先创建任何这些实例

但是,如果您的 AccessTokenRefreshDataSourceImpl 不需要在构造函数本身内使用其 AuthenticationService 实例,您可以将其替换为: Dagger 自动允许您为图中的任何 TProvider<AuthenticationService>注入,以及其他有用的绑定。这允许 Dagger 创建 AccessTokenRefreshDataSource,而无需首先创建 AuthenticationService,并承诺一旦创建了对象图,您的 AccessTokenRefreshDataSource 就可以接收它所需的单例 AuthenticationService 实例。注入提供程序后,只需在需要的地方调用即可获取实例(大概在构造函数之外)。Provider<T>authenticationServiceProvider.get()

当然,您可以在您控制的图表中的其他任何地方使用相同的重构来解决您的问题。AccessTokenAuthenticator 也是一个合理的重构点,假设您自己编写了它,因此可以修改它的构造函数。


评论中讨论的要点:

  • 您始终可以在图中注入 aProvider<T>而不是任何绑定。T除了对于打破依赖循环很有价值之外,如果您的依赖注入类需要实例化任意数量的该对象,或者如果创建该对象需要大量内存或类加载并且您希望将其延迟到之后。当然,如果该对象的构造成本很低且没有依赖循环,并且您希望get()只调用一次,那么您可以跳过它并直接注入,T就像您在此处所做的那样。
    • Provider<T>是一个单方法对象。调用它与调用组件本身类型get()的 getter 相同。T如果该对象没有作用域,您将得到一个新对象;如果该对象是有作用域的,您将获得 Dagger 存储在组件中的对象。

    • 一般来说,您可以直接注入 Provider 并get直接调用它:

      class AccessTokenRefreshDataSourceImpl @Inject constructor(
        private val authenticationServiceProvider:
          Provider<AuthenticationService>,
        private val userPrefsDataSource: UserPrefsDataSource,
      ) : AccessTokenRefreshDataSource {
      
      Run Code Online (Sandbox Code Playgroud)

      ...然后不要this.authenticationService.someMethod()直接使用,而是使用this.authenticationServiceProvider.get().someMethod(). Ma3x 在评论中指出,如果您将其声明val authenticationService get() = authenticationServiceProvider.get()为类字段,Kotlin 可以抽象出对涉及的调用这一事实get(),并且您无需对 AccessTokenRefreshDataSourceImpl 进行任何其他更改。

    • 您还需要更改@Provides模块中的方法,但这只是因为您没有充分利用@InjectAccessTokenRefreshDataSourceImpl 上的注释,如下所示。

      @Singleton
      @Provides
      fun providesAccessTokenRefreshDataSource(
          userPrefsDataSource: UserPrefsDataSource,
          authenticationServiceProvider: Provider<AuthenticationService>,  // here
      ): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
          authenticationServiceProvider /* and here */, userPrefsDataSource
      )
      
      Run Code Online (Sandbox Code Playgroud)
  • 通常不需要使用@Provides来引用@Inject带注释的构造函数。@Provides当您无法更改构造函数来实现它时很有用@Inject@Inject可以减少维护,因为这样您就不需要将@Provides方法参数复制到构造函数;Dagger 会为你做到这一点。在这里阅读更多内容。
    • 如果您确实使用@Inject并删除了您的@Provides方法,您可能仍然希望使用@Binds来指示您的 AccessTokenRefreshDataSource 应绑定到 AccessTokenRefreshDataSourceImpl,尽管您需要决定如何将@Binds和放在@Provides同一模块中。在 Java 8 中,您可以通过创建@Provides方法static并将它们放在接口上来做到这一点,但在 Kotlin 中,创建嵌套接口并使用@Module(includes = ...). 在这里阅读更多内容。