7

In my case, I need to change URL dynamically, but I don't want to create 2 instances of the retrofit client. I'm trying to change base URL via interceptor modifications, but retrofit still uses the old value. What am I doing wrong?

App.java

public class App extends Application {
    private static AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        appComponent =
                DaggerAppComponent
                        .builder()
                        .appModule(new AppModule(this))
                        .build();

    }

    @NonNull
    public static App get(@NonNull Context context) {
        return (App) context.getApplicationContext();
    }

    public static AppComponent getAppComponent() {
        return appComponent;
    }
}

AppModule.java

@Module
public class AppModule {
    private Context context;

    public AppModule(Context context) {
        this.context = context;
    }

    @Provides
    @Singleton
    Context provideContext() {
        return context;
    }
    
}

NetModule.java

@Module
public class NetModule {

    @Provides
    @Singleton
    Gson provideGson() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
        return gsonBuilder.create();
    }
    @Provides
    @Singleton
    MainInterceptor provideMyApiInterceptor() {
        return MainInterceptor.get();
    }

    @Provides
    @Singleton
    OkHttpClient provideOkhttpClient() {
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        client.readTimeout(15, TimeUnit.SECONDS);
        client.connectTimeout(20, TimeUnit.SECONDS);
        return client.build();
    }

    @Provides
    @Singleton
    Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
        return new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("first.url.com")
                .client(okHttpClient)
                .build();
    }
}

NetComponent.java

@Singleton
@Component(modules = NetModule.class)
public interface NetComponent {
    Retrofit getRetrofit();

    MainInterceptor getInterceptor();
}

MainInterceptor.class

public class MainInterceptor implements Interceptor {
    private static MainInterceptor sInterceptor;
    private String mScheme;
    private String mHost;

    public static MainInterceptor get() {
        if (sInterceptor == null) {
            sInterceptor = new MainInterceptor();
        }
        return sInterceptor;
    }
    
    private MainInterceptor() {
    }

    public void setInterceptor(String url) {
        HttpUrl httpUrl = HttpUrl.parse(url);
        mScheme = httpUrl.scheme();
        mHost = httpUrl.host();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();

        if (mScheme != null && mHost != null) {
            HttpUrl newUrl = original.url().newBuilder()
                    .scheme(mScheme)
                    .host(mHost)
                    .build();
            original = original.newBuilder()
                    .url(newUrl)
                    .build();
        }
        return chain.proceed(original);
    }
}

And this is the code to initialize the component in some class.

NetComponent component= DaggerNetComponent.create();
DataService service = component.getRetrofit().create(DataService.class);
MainInterceptor interceptor = component.getInterceptor();
interceptor.setInterceptor("second.url.com");
service.getSomeData();

After that, the URL is still "first.url.com"

AgentP
  • 6,261
  • 2
  • 31
  • 52
Mikhail
  • 800
  • 8
  • 21

4 Answers4

8

The simple way is to use @Named:

@Provides
@Singleton
@Named("retrofit_1")
Retrofit provideRetrofit1(Gson gson, OkHttpClient okHttpClient) {
        return new Retrofit.Builder()
                ...
                .baseUrl("url_1.com")
                ...;}

@Provides
@Singleton
@Named("retrofit_2")
Retrofit provideRetrofit2(Gson gson, OkHttpClient okHttpClient) {
        return new Retrofit.Builder()
                ...
                .baseUrl("url_2.com")
                ...;}

then use the same @Named:

@Provides
IApi1 provideApi_1(@Named("retrofit_1") RestAdapter adapter){....}

@Provides
IApi2 provideApi_2(@Named("retrofit_2") RestAdapter adapter){....}
Siarhei
  • 2,358
  • 3
  • 27
  • 63
2

When you provide a Retrofit, you already set baseUrl "first.url.com"

return new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl("first.url.com")
            .client(okHttpClient) //<------ It not OkHttp with your MainInterCeptor
            .build();

And fun fact is, your MainInterceptor which is set-up with new url is unrelated to your Retrofit, because Retrofit was built with OkHttp

@Provides
@Singleton
OkHttpClient provideOkhttpClient() {
    OkHttpClient.Builder client = new OkHttpClient.Builder();
    client.readTimeout(15, TimeUnit.SECONDS);
    client.connectTimeout(20, TimeUnit.SECONDS);
    return client.build();
}

If you want to dynamically change your BASE_URL, there are many ways for you to do that.

public class ApiConstant {

    public static String BASE_URL = "yourUrl";

    private ApiConstant() {
    }
}

Create a @Scope for Retrofit because it can be changed

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}

Then use it when building Retrofit

@Provides
@PerActivity
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
    return new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl("first.url.com")
            .client(ApiConstant.BASE_URL)
            .build();
}

Then change the BASE_URL when init DaggerActivityComponent. (Or create a new @Scope for new url)

Read this documentation for more detail

Hope this help!

phatnhse
  • 3,870
  • 2
  • 20
  • 29
  • Just a quick question, the provideRetrofit would only ever be run once, since its a Singleton. How does the URL change then ? (Assuming, I achanged, ApiConstant.BASE_URL ? – Robert J. Clegg Jul 27 '17 at 15:46
  • Thanks for notice me that. You can change that String or use the second approach – phatnhse Jul 27 '17 at 15:52
  • I get that you can change the String. But the instance of Retrofit would still point to the old URL, not so? – Robert J. Clegg Jul 27 '17 at 15:54
  • No. Each time you run new activity or fragment, you have to rebuild your DaggerActivityComponent, you can change the url before init it – phatnhse Jul 27 '17 at 15:55
  • The second approach would be better, the only reason that make that a little bit harder is, url is String, and when you want to declare new String, you have create a new annotation (Retention.Runtime) for it – phatnhse Jul 27 '17 at 15:57
  • I don't believe that is the way it works. @ Singleton denotes that this code will only execute once. The whole point of it, is not to provide multiple instances of Retrofit. This is just my understanding of it. It seems silly to say its a Singleton then, when it really isn't. – Robert J. Clegg Jul 27 '17 at 15:59
  • Yes, you're right, I'm sorry. I forget that I did provide a Singleton. – phatnhse Jul 27 '17 at 16:03
1

Add Interceptor in your AppModule

@Provides
@Singleton
OkHttpClient provideOkHttpClient(Cache cache, MainInterceptor interceptor) {
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .cache(cache)
            .build();
    return okHttpClient;
}

and then in your activity or presenter set url before calling retrofit service

mInterceptor.setInterceptor(urlname);
mRetrofitService.call();
eurosecom
  • 2,932
  • 4
  • 24
  • 38
-1

If you already have a base URL set but you want to override it for just one API call and not all of them, it can be done pretty easily.

@PUT("https://my-api.com/user")
fun cancelOrder(@Path("user") user: String): Single<MyResponse>

Works the same way for POST and GET as well.

div
  • 1,475
  • 3
  • 22
  • 32