11

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.

JakubW
  • 1,101
  • 1
  • 20
  • 37
  • This is all your singleton ? Where do you do your `retrofit.create()` ? – Raphael Teyssandier Apr 21 '16 at 12:41
  • Yes this is the singleton responsible for providing retrofit for my presenters. I do have more singletons but they are not connected to the problem. EDIT: I've added my create() singleton to question code. – JakubW Apr 21 '16 at 12:44
  • Have try to do like, `baseUrl()` and in your request `@POST("{url}")`, then url equals to an arguments to your function like, `@Path("url") String url` ? Just an idea. Can you show how do you make a call to your API ? – Raphael Teyssandier Apr 21 '16 at 12:50
  • I think the only way to instert full url via annotation in API interface is with `@URL` and that can be used only in `@GET` calls. – JakubW Apr 21 '16 at 12:52
  • `provideRetrofit` does way too much. Providing the url, the okhttp client, and each of the 2 call adapters and the header interceptor should all be its own method – David Medenjak Apr 21 '16 at 17:56
  • @JakubW Not only on `@GET` calls. I am using this approach with `@Url` annotation right now with my `@POST` calls and I replaced it with overhead implementation of HostInterceptor + OkHttp + Dagger in my case. But solution above still can be useful when you want to change host only except whole path. – Augusent Apr 24 '16 at 18:03
  • https://stackoverflow.com/a/63076030/7511020 look into here – Zeeshan Akhtar Jul 24 '20 at 14:51

2 Answers2

16

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.

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
5

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.

Godfather
  • 833
  • 9
  • 14
  • 1
    This has changed. This is a gist implementing the new behaviour https://gist.github.com/swankjesse/8571a8207a5815cca1fb – Arthur May 16 '16 at 21:29