I have been using the dagger 2.11 AndroidInjection with no problems until i tried to Inject my Retrofit service in an OkHttp Authenticator in order to handle session timeouts.
AndroidInjection allows only for injections in Activities,Fragments,Services,ContentProviders and BroadcastReceicers.
My questions is how can i use the new Dagger AndroidInjection in a class that is not in the above list?
Just use inject to provide Retrofit, in the same module you provide also okhttp that is used by retrofit
#Singleton
#Provides
OkHttpClient providesOkHttpClient(){
return new OkHttpClient.Builder()
.connectTimeout(RETROFIT_API_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(RETROFIT_API_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(RETROFIT_API_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.build();
}
#Singleton
#Provides
Retrofit providesRetrofit(OkHttpClient client){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.build();
}
Then in your class:
public class MyClass {
Retrofit mRetrofit;
#Inject
public MyClass(Retrofit retrofit){
mRetrofit = retrofit;
}
}
Related
I have Hilt DI framework in my Android project. Also I have retrofit, and ApiNetworkModule for getting singleton retrofit object:
#Module
#InstallIn(SingletonComponent.class)
public class ApiNetworkModule {
...
#Singleton
#Provides
public Retrofit provideRetrofit(
OkHttpClient okHttpClient,
Gson gson,
SharedPrefManager sharedPrefManager
) {
return new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(sharedPrefManager.getUrl())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
...
}
In general it works fine, but I have noticed in the case when url is updated in sharedPrefManager, retrofit object does not know about it and uses old url. It will be fully updated only after closing-opening application. Is there any way how to reinit Retrofit singleton programmatically? Or how to handle it correctly?
I'm learning about retrofit interceptors, for work purposes I'm using dagger-hilt for the injection of dependencies to fragments etc. I wrote a custom interceptor to check for connection errors and I'm trying to add it to the Retrofit.Builder():
#Provides
#Singleton
fun provideApi(): StoreApi {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
.create(StoreApi::class.java)
}
however, I have no clue how to pass that:
val okHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(ConnectivityInterceptor)
.build()
as a .client() to the retofit builder (even with dagger-hilt), any ideas?
You can setup a module something like this where you provide all the dependency requirements as functions and exposing them to dagger through #Provides then leave dagger to provide the dependencies as function arguments to build the dependency graph :
#Module class ApiModule {
#Provides
#Singleton
internal fun provideApi(retrofit: Retrofit): StoreApi {
return retrofit
.create(StoreApi::class.java)
}
#Provides
#Singleton
internal fun retrofit(client: OkHttpClient): Retrofit =
Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
#Provides
#Singleton
internal fun client(connectivityInterceptor: ConnectivityInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(connectivityInterceptor)
.build()
#Provides
#Singleton
internal fun interceptor(): ConnectivityInterceptor = ConnectivityInterceptor()
}
This is a trivial example based on the supplied code.
I have an MVVM program that uses Retrofit and Hilt
I have two questions:
Why the most examples the Retrofit was created in object form instead of class form?
Why we shouldn't implements (inheritance) the AppModule from Retrofit to make a limitation for accessing the Retrofit directly?
My personal experience leads me to write it that way, but I have never seen something like it.
I want to know about expert programmers' opinions on problems in my suggested way.
Retrofit class:
open class BaseApi protected constructor(){
private fun createBuilder(baseUrl : String): Retrofit {
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
protected fun getWeatherService(): WeatherService {
val weatherServiceBaseAddress = "https://api.weatherapi.com/"
return createBuilder(weatherServiceBaseAddress).create(WeatherService::class.java)
}
}
AppModule:
#Module
#InstallIn(SingletonComponent::class)
class AppModule : BaseApi() {
#Singleton
#Provides
fun weatherServiceProvider() = getWeatherService()
}
Because, retrofit it self actually a class that generate retrofit instance (if you see, retrofit does not needed some context).
And no 2, if you have learned or reading about SOLID programming, you will know.
then if you use it on AppModule, it will very difficult to maintain (just imagine your app will be have so much module and you put all of your module in app module)
This question already has answers here:
Dagger 2 injecting multiple instances of same object type
(2 answers)
Closed 1 year ago.
I use dagger to create instances for application
But due to some requirements, i need multiple instances of same object,
Can any one suggest good way of doing that ?
You can use #Named annotation to achieve this behaviour,
Example:
Step 1:
Declare your method with #Named annotation with unique key
#Provides
#Singleton
#Named("cached")
fun someInstance() = return someInstance()
Step 2:
Use the instance with the declared key
#Inject #Named("cached")
val instance: someInstance
You can use #Named("") to create multiple instances. Here I create 2 instances of Interceptor with different #Named("")
Java:
#InstallIn(SingletonComponent.class)
#Module
class NetworkModule {
#Provides
#Singleton
#Named("headerInterceptor")
private Interceptor provideInterceptor(UserManager userManager) {
return new Interceptor() {
#NonNull
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
return chain.proceed(chain.request());
}
};
}
#Provides
#Singleton
#Named("loggingInterceptor")
private Interceptor provideLoggingInterceptor() {
return new LoggingInterceptor.Builder()
.loggable(BuildConfig.DEBUG)
.setLevel(Level.BODY)
.log(Platform.INFO)
.request("Request")
.response("Response")
.build();
}
#Provides
#Singleton
private OkHttpClient provideOkHttpClient(
Cache cache,
#Named("headerInterceptor") Interceptor interceptor,
#Named("loggingInterceptor") Interceptor loggingInterceptor
) {
return new OkHttpClient.Builder()
.cache(cache)
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(loggingInterceptor)
.build();
}
}
Kotlin
#InstallIn(SingletonComponent::class)
#Module
class NetworkModule {
#Provides
#Singleton
#Named("headerInterceptor")
fun provideInterceptor(userManager: UserManager): Interceptor {
return Interceptor { chain ->
chain.proceed(chain.request())
}
}
#Provides
#Singleton
#Named("loggingInterceptor")
fun provideLoggingInterceptor(): Interceptor {
return LoggingInterceptor.Builder()
.loggable(BuildConfig.DEBUG)
.setLevel(Level.BODY)
.log(Platform.INFO)
.request("Request")
.response("Response")
.build()
}
#Provides
#Singleton
fun provideOkHttpClient(
cache: Cache,
#Named("headerInterceptor") interceptor: Interceptor,
#Named("loggingInterceptor") loggingInterceptor: Interceptor
): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(loggingInterceptor)
.build()
}
}
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:
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.
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.
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.