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.
Your problem is:
Hence the circular dependency.
One possible solution here is for your
TokenAuthenticator
to depend on anAPIServiceHolder
rather than aAPIService
. Then yourTokenAuthenticator
can be provided as a dependency when configuringOKHttpClient
regardless of whether theAPIService
(further down the object graph) has been instantiated or not.A very simple APIServiceHolder:
Then refactor your TokenAuthenticator:
Note that the code to retrieve the token should be synchronous. This is part of the contract of
Authenticator
. The code inside theAuthenticator
will run off the main thread.Of course you will need to write the
@Provides
methods for the same:And refactor the provider methods:
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.
Big thanks to @Selvin and @David. I have two approach, one of them is David's answer and the other one is :
Creating another
OkHttp
orRetrofit
or another library which will handle our operations insideTokenAuthenticator
class.If you want to use another
OkHttp
orRetrofit
instance you must use Qualifier annotation.For example :
then provide :
Then we can provide our seperated interfaces :
When providing our TokenAuthenticator :
Advantages : You have two seperated api interfaces which means you can maintain them independently. Also you can use plain
OkHttp
orHttpUrlConnection
or another library.Disadvantages : You will have two different OkHttp and Retrofit instance.
P.S : Make sure you make syncronous calls inside Authenticator class.
You can inject the service dependency into your authenticator via the Lazy type. This way you will avoid the cyclic dependency on instantiation.
Check this link on how Lazy works.