Android Dagger2 + OkHttp + Retrofit dependency cycle error

android, dagger-2, dependency-injection, retrofit2, rx-java2

Solution

Your problem is:

- Your OKHttpClient depends on your Authenticator

- Your Authenticator depends on a Retrofit Service

- Retrofit depends on an OKHttpClient (as in point 1)

Hence the circular dependency.

One possible solution here is for your `TokenAuthenticator` to depend on an `APIServiceHolder` rather than a `APIService`. Then your `TokenAuthenticator` can be provided as a dependency when configuring `OKHttpClient` regardless of whether the `APIService` (further down the object graph) has been instantiated or not.

A very simple APIServiceHolder:

public class APIServiceHolder {

    private APIService apiService;

    @Nullable
    APIService apiService() {
        return apiService;
    }

    void setAPIService(APIService apiService) {
        this.apiService = apiService;
    }
}

Then refactor your TokenAuthenticator:

@Inject
public TokenAuthenticator(@NonNull APIServiceHolder apiServiceHolder, @NonNull ImmediateSchedulerProvider schedulerProvider) {
    this.apiServiceHolder = apiServiceHolder;
    this.schedulerProvider = schedulerProvider;
    this.disposables = new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    if (apiServiceHolder.get() == null) {
         //we cannot answer the challenge as no token service is available

         return null //as per contract of Retrofit Authenticator interface for when unable to contest a challenge
    }    

    request = null;            

    TokenResponse tokenResponse = apiServiceHolder.get().blockingGet()

    if (tokenResponse.isSuccessful()) {
        saveUserToken(tokenResponse.body());
        request = response.request().newBuilder()
                     .header("Authorization", getUserAccessToken())
                     .build();
    } else {
       logoutUser();
    }

    return request;
}

Note that the code to retrieve the token should be synchronous. This is part of the contract of `Authenticator`. The code inside the `Authenticator` will run off the main thread.

Of course you will need to write the `@Provides` methods for the same:

@Provides
@ApplicationScope
apiServiceHolder() {
    return new APIServiceHolder();
}

And refactor the provider methods:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit, APIServiceHolder apiServiceHolder) {
    APIService apiService = retrofit.create(APIService.class);
    apiServiceHolder.setAPIService(apiService);
    return apiService;
}

Note that mutable global state is not usually a good idea. However, if you have your packages organised well you may be able to use access modifiers appropriately to avoid unintended usages of the holder.

Problem

Hey there I am using `Dagger2`, `Retrofit` and `OkHttp` and I am facing dependency cycle issue. When providing `OkHttp` : ``` @Provides @ApplicationScope OkHttpClient provideOkHttpClient(TokenAuthenticator auth,Dispatcher dispatcher){ return new OkHttpClient.Builder() .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS) .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS) .authenticator(auth) .dispatcher(dispatcher) .build(); } ``` When providing `Retrofit` : ``` @Provides @ApplicationScope Retrofit provideRetrofit(Resources resources,Gson gson, OkHttpClient okHttpClient){ return new Retrofit.Builder() .baseUrl(resources.getString(R.string.base_api_url)) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); } ``` When providing `APIService` : ``` @Provides @ApplicationScope APIService provideAPI(Retrofit retrofit) { return retrofit.create(APIService.class); } ``` My `APIService` interface : ``` public interface APIService { @FormUrlEncoded @POST("token") Observable<Response<UserTokenResponse>> refreshUserToken(); --- other methods like login, register --- } ``` My `TokenAuthenticator` class : ``` @Inject public TokenAuthenticator(APIService mApi,@NonNull ImmediateSchedulerProvider mSchedulerProvider) { this.mApi= mApi; this.mSchedulerProvider=mSchedulerProvider; mDisposables=new CompositeDisposable(); } @Override public Request authenticate(Route route, Response response) throws IOException { request = null; mApi.refreshUserToken(...) .subscribeOn(mSchedulerProvider.io()) .observeOn(mSchedulerProvider.ui()) .doOnSubscribe(d -> mDisposables.add(d)) .subscribe(tokenResponse -> { if(tokenResponse.isSuccessful()) { saveUserToken(tokenResponse.body()); request = response.request().newBuilder() .header("Authorization", getUserAccessToken()) .build(); } else { logoutUser(); } },error -> { },() -> {}); mDisposables.clear(); stop(); return request; } ``` My logcat : ``` Error:(55, 16) error: Found a dependency cycle: com.yasinkacmaz.myapp.service.APIService is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideTokenAuthenticator(…, mApi, …) com.yasinkacmaz.myapp.service.token.TokenAuthenticator is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideOkHttpClient(…, tokenAuthenticator, …) okhttp3.OkHttpClient is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideRetrofit(…, okHttpClient) retrofit2.Retrofit is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideAPI(retrofit) com.yasinkacmaz.myapp.service.APIService is provided at com.yasinkacmaz.myapp.darkvane.components.ApplicationComponent.exposeAPI() ``` So my question: My `TokenAuthenticator` class is depends on `APIService` but I need to provide `TokenAuthenticator` when creating `APIService`. This causes dependency cycle error. How do I beat this , is there anyone facing this issue ? Thanks in advance.

Original source

Related problems