简体   繁体   中英

How to remove Dagger Hilt dependency Injection cycle

I have a.network module class that provides an ApiService instance. There is an Authenticator class which refreshes access token when expired. The authenticator requires ApiService instance for making API calls. 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. How to properly inject ApiService into TokenExpiryAuthenticator without causing cyclic dependency?

@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. This AuthenticationApi is created with an OkHttp instance without your Authenticator.

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.

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()
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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