简体   繁体   English

如何删除 Dagger Hilt 依赖注入循环

[英]How to remove Dagger Hilt dependency Injection cycle

I have a.network module class that provides an ApiService instance.我有一个提供ApiService实例的网络模块 class。 There is an Authenticator class which refreshes access token when expired.有一个Authenticator class,它在过期时刷新访问令牌。 The authenticator requires ApiService instance for making API calls.身份验证器需要ApiService实例来进行 API 调用。 This causes a cyclic dependency.这会导致循环依赖。 How to avoid this?如何避免这种情况?

Now I'm creating a new ApiService inside TokenExpiryAuthenticator class to make API calls, to break the cyclic dependency.现在我在TokenExpiryAuthenticator class 中创建一个新的ApiService来进行 API 调用,以打破循环依赖。 How to properly inject ApiService into TokenExpiryAuthenticator without causing cyclic dependency?如何正确地将ApiService注入TokenExpiryAuthenticator而不会导致循环依赖?

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

@Provides
@Singleton
@Named("Other")
fun provideRetrofitWithoutInterceptor(@Named("Other") client: OkHttpClient, gson: Gson): Retrofit {
    return Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
}

@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient, gson: Gson): Retrofit {
    return Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
}

@Provides
@Singleton
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, supportInterceptor: SupportInterceptor, tokenExpiryAuthenticator: TokenExpiryAuthenticator): OkHttpClient {
    return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
        .readTimeout(1, TimeUnit.MINUTES)
        .callTimeout(1, TimeUnit.MINUTES)
        .addInterceptor(httpLoggingInterceptor)
        .addInterceptor(supportInterceptor)
        .authenticator(tokenExpiryAuthenticator)
        .build()
}

@Named("Other")
@Provides
@Singleton
fun providesOkHttpClientWithoutInterceptor(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
    return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
        .readTimeout(1, TimeUnit.MINUTES)
        .callTimeout(1, TimeUnit.MINUTES)
        .addInterceptor(httpLoggingInterceptor)
        .build()
}

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

@Provides
@Singleton
fun providesGson(): Gson {
    return GsonBuilder().create()
}

@Provides
@Singleton
fun providesRestApiService(retrofit: Retrofit): ApiService {
    return retrofit.create(ApiService::class.java)
}

@Provides
@Singleton
@Named("Other")
fun providesRestApiServiceWithoutInterceptor(@Named("Other") retrofit: Retrofit): ApiService{
    return retrofit.create(ApiService::class.java)
}

} }

You could split your ApiService and create a new AuthenticationApi which only includes the endpoints to authenticate or refresh your access tokens.您可以拆分 ApiService 并创建一个新的 AuthenticationApi,它仅包含用于验证或刷新访问令牌的端点。 This AuthenticationApi is created with an OkHttp instance without your Authenticator.此 AuthenticationApi 是使用没有 Authenticator 的 OkHttp 实例创建的。

That way your Authenticator only needs reference to this slim Retrofit api. This also guarantees that you don't get HTTP 401 authentication loops in case of wrong credentials when using your authentication endpoints.这样你的身份验证器只需要参考这个苗条的 Retrofit api。这也保证你不会得到 HTTP 401 身份验证循环,以防在使用身份验证端点时出现错误的凭据。

Simplest possible solution is to inject the dependency lazily.最简单的解决方案是延迟注入依赖项。

class TokenExpiryAuthenticator @Inject constructor(
  private val api: Lazy<ApiService>,
  private val persistence: TokenPersistenceRepository
) : Authenticator {

  override fun authenticate(route: Route?, response: Response): Request? {

    // If HTTP 401 error was raised while trying to refresh token it means
    // the token is invalid (or expired). Sign out user and do not
    // proceed with further requests.
    if (response.request.url.encodedPath == REFRESH_TOKEN_PATH) {

      // Perform any cleanup needed.
      
      // Return null so no further requests will be performed.
      return null
    }

    // Request failed for old token. It's about time to refresh it.
    if (response.request.header(AUTH_HEADER) != null) {
      
      // Refresh access token using your lazily injected service.
      persistence
        .retrieveRefreshToken()
        .flatMap(api.get()::refreshToken)
        .flatMapCompletable {
          Completable.concatArray(
            persistence.saveAccessToken(it.accessToken),
            persistence.saveRefreshToken(it.refreshToken),
          )
        }
        .blockingAwait()
        // You can use blocking await as the [authenticate] method is
        // run on I/O thread anyway.
    }

    // Load recently refreshed access token.
    val token = (...)
    
    // Format authorization header value.
    val header = (...)
    
    // Modify and proceed with a request with refreshed access token.
    return response.request.newBuilder()
      .removeHeader(AUTH_HEADER)
      .addHeader(AUTH_HEADER, header)
      .build()
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM