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.