Hey there I am using Dagger2
, Retrofit
and OkHttp
and I am facing dependency cycle issue.
When providing OkHttp
:
Using the Lazy
interface of Dagger 2 is the solution here.
In your TokenAuthenticator
replace APIService mApi
with Lazy<APIService> mApiLazyWrapper
@Inject
public TokenAuthenticator(Lazy<APIService> mApiLazyWrapper,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
this.mApiLazyWrapper= mApiLazyWrapper;
this.mSchedulerProvider=mSchedulerProvider;
mDisposables=new CompositeDisposable();
}
And to get the APIService
instance from wrapper use mApiLazyWrapper.get()
In case mApiLazyWrapper.get()
returns null, return null from the authenticate
method of TokenAuthenticator
as well.
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
or Retrofit
or another library which will handle our operations inside TokenAuthenticator
class.
If you want to use another OkHttp
or Retrofit
instance you must use Qualifier annotation.
For example :
@Qualifier
public @interface ApiClient {}
@Qualifier
public @interface RefreshTokenClient {}
then provide :
@Provides
@ApplicationScope
@ApiClient
OkHttpClient provideOkHttpClientForApi(TokenAuthenticator tokenAuthenticator, TokenInterceptor tokenInterceptor, 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(tokenAuthenticator)
.addInterceptor(tokenInterceptor)
.dispatcher(dispatcher)
.build();
}
@Provides
@ApplicationScope
@RefreshTokenClient
OkHttpClient provideOkHttpClientForRefreshToken(Dispatcher dispatcher){
return new OkHttpClient.Builder()
.connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
.writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
.dispatcher(dispatcher)
.build();
}
@Provides
@ApplicationScope
@ApiClient
Retrofit provideRetrofitForApi(Resources resources, Gson gson,@ApiClient OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(resources.getString(R.string.base_api_url))
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
@Provides
@ApplicationScope
@RefreshTokenClient
Retrofit provideRetrofitForRefreshToken(Resources resources, Gson gson,@RefreshTokenClient OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(resources.getString(R.string.base_api_url))
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
Then we can provide our seperated interfaces :
@Provides
@ApplicationScope
public APIService provideApi(@ApiClient Retrofit retrofit) {
return retrofit.create(APIService.class);
}
@Provides
@ApplicationScope
public RefreshTokenApi provideRefreshApi(@RefreshTokenClient Retrofit retrofit) {
return retrofit.create(RefreshTokenApi.class);
}
When providing our TokenAuthenticator :
@Provides
@ApplicationScope
TokenAuthenticator provideTokenAuthenticator(RefreshTokenApi mApi){
return new TokenAuthenticator(mApi);
}
Advantages : You have two seperated api interfaces which means you can maintain them independently. Also you can use plain OkHttp
or HttpUrlConnection
or another library.
Disadvantages : You will have two different OkHttp and Retrofit instance.
P.S : Make sure you make syncronous calls inside Authenticator class.
Your problem is:
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.
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.