PROBLEM
I need to call API from domains entered by USER and I need to edit my Retrofit
singleton before the call accordingly to the inserted data.
Is there a way to "reset" my singleton, forcing it to recreate?
or
Is there a way to update my baseUrl
with my data (maybe in Interceptor?) just before call?
CODE
Singletons
@Provides
@Singleton
Retrofit provideRetrofit(SharedPreferences prefs) {
String apiUrl = "https://%1s%2s";
apiUrl = String.format(apiUrl, prefs.getString(ACCOUNT_SUBDOMAIN, null), prefs.getString(ACCOUNT_DOMAIN, null));
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new HeaderInterceptor())
.build();
return new Retrofit.Builder()
.baseUrl(apiUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
@Provides
@Singleton
API provideAPI(Retrofit retrofit) {
return retrofit.create(API.class);
}
API
@FormUrlEncoded
@POST("endpoint")
Observable<Response> logIn(@Field("login") String login, @Field("password") String password);
How it works now
Well the idea was to save user domain data via SharedPrefs
before API call and modify baseUrl
with formatted String.
I see 2 options here:
- Use dagger as it is intended. Create for every
baseUrl
their own Retrofit
client, or
- Use an interceptor to modify the request before sending it
Dagger approach
If you were to brute force urls, this would probably not be the right choice, since it relies on creating a new Retrofit
instance for each.
Now every time the url changes, you just recreate the following demonstrated UrlComponent
by supplying it with a new UrlModule
.
Clean up
Clean your @Singleton
module, so that it provides GsonConverterFactory
, and RxJavaCallAdapterFactory
to make proper use of dagger and not recreate shared objects.
@Module
public class SingletonModule {
@Provides
@Singleton
GsonConverterFactory provideOkHttpClient() {/**/}
@Provides
@Singleton
RxJavaCallAdapterFactory provideOkHttpClient() {/**/}
}
@Singleton
@Component(modules = SingletonModule.class)
interface SingletonComponent {
// sub component
UrlComponent plus(UrlModule component);
}
Url Scoped
Introduce a @UrlScope
to scope your Retrofit
instances.
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UrlScope {
}
Then create a subcomponent
@SubComponent(modules=UrlModule.class)
public interface UrlComponent {}
And a module for it
@Module
class UrlModule {
private final String mUrl;
UrlModule(String url) { mUrl = url; }
@Provides
String provideUrl() {
return mUrl;
}
@Provides
@UrlScope
OkHttpClient provideOkHttpClient(String url) {
return new OkHttpClient.Builder().build();
}
@Provides
@UrlScope
Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder().build();
}
}
Use scoped Retrofit
Instantiate the component and use it.
class Dagger {
public void demo() {
UrlModule module = new UrlModule(/*some url*/);
SingletonComponent singletonComponent = DaggerSingletonComponent.create();
UrlComponent urlComponent = singletonComponent.plus(module);
urlComponent.getRetrofit(); // done.
}
}
OkHttp approach
Provide a properly scoped interceptor (@Singleton
in this case) and implement the corresponding logic.
@Module
class SingletonModule {
@Provides
@Singleton
GsonConverterFactory provideGsonConverter() { /**/ }
@Provides
@Singleton
RxJavaCallAdapterFactory provideRxJavaCallAdapter() { /**/ }
@Provides
@Singleton
MyApiInterceptor provideMyApiInterceptor() { /**/ }
@Provides
@Singleton
OkHttpClient provideOkHttpClient(MyApiInterceptor interceptor) {
return new OkHttpClient.Builder().build();
}
@Provides
@Singleton
Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder().build();
}
}
@Singleton
@Component(modules = SingletonModule.class)
interface SingletonComponent {
Retrofit getRetrofit();
MyApiInterceptor getInterceptor();
}
todo Implement the MyApiInterceptor
. You will need to have a setter for the base url, and then just rewrite / modify the requests coming through.
Then, again, just go ahead and use it.
class Dagger {
public void demo() {
SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MyService service = singletonComponent.getRetrofit().create(MyService.class);
MyApiInterceptor interceptor = singletonComponent.getInterceptor();
interceptor.setBaseUrl(myUrlA);
service.doA();
interceptor.setBaseUrl(someOtherUrl);
service.doB();
}
}
As a third approach, you could also use reflection to just directly change base the base URL—I added this last just for completeness.
You can implement BaseUrl
and pass that instead of a fixed URL.check out this link
Other approach is implementing Endpoint
and make use of setUrl()
.So for changing some header value at run time then you can use interceptor and add it to OkHttp.