49

I have a scenario where I have to call an API with the same base URL, e.g. www.myAPI.com but with a different baseUrl.

I have an instance of Retrofit 2 which is built via a Builder:

return new Retrofit.Builder()
    .baseUrl(FlavourConstants.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .client(okHttpClient)
    .build();

The FlavourConstants.BASE_URL looks like this:

public static final String BASE_URL = "http://myApi.development:5000/api/v1/";

For some WebRequests, I must call the same API but on others, I must call it from a completely different BaseUrl. How do I change the Retrofit instance to therefore point to a different URL during runtime?

The Retrofit instance doesn't have a .setBaseUrl or setter or anything similar as it's built via a Builder.

Any ideas?

Kaaveh Mohamedi
  • 1,399
  • 1
  • 15
  • 36
Subby
  • 5,370
  • 15
  • 70
  • 125
  • 1
    as you have mentioned already, the `Retrofit` instance is kind of immutable (kind of what Builders are meant for). So you'd need to create another instance for the other URL you'd like to set. – asgs Aug 06 '16 at 16:58
  • https://stackoverflow.com/a/63076030/7511020 if you are not using dagger2, then just see only step1 and add your interceptor into HttpClient Builder object – Zeeshan Akhtar Jul 24 '20 at 14:55

10 Answers10

30

Lucky for you Retrofit have a simple solution for that:

public interface UserManager {  
    @GET
    public Call<ResponseBody> userName(@Url String url);
}

The url String should specify the full Url you wish to use.

Nir Duan
  • 6,164
  • 4
  • 24
  • 38
  • 4
    This only works for `GET` methods and not for `HTTP` methods such as `@POST`. I have solved the issue and will post an answer once i've verified that it works. – Subby Aug 07 '16 at 17:10
  • 1
    @Subby did you verified it worked? Where is your answer? – Neon Warge Oct 02 '18 at 01:13
  • 3
    This is not the solution to the original problem, it does not change the base URL (and it also pollutes the API). It keeps the original base URL, but allows you to completely change the endpoint for one particular call - it's a workaround but it does not solve the problem at hand. (and yes, I just tried it, it works) – milosmns May 03 '19 at 14:15
  • @Subby why this answer does not work with `@POST`? – Bitwise DEVS Mar 20 '22 at 07:44
  • Any links to the docs you can add to show the scope and working of `@Url` parameter-annotation? – hc_dev May 12 '23 at 06:23
21

Retrofit 2.4, MAY 2019

Two simple solution for this hassle are:

  1. Hardcode the new URL, while leaving the base URL as it is:

    @GET("http://example.com/api/")
    Call<JSONObject> callMethodName();
    
  2. Pass the new URL as an argument, while leaving the base URL as it is:

    @GET
    Call<JSONObject> callMethodName(@Url String url);
    

N.B: These methods work for GET or POST. However, this solution is only efficient if you just need to use an exception of one or two different URLs than your base URL. Otherwise, things can get a little messy in terms of code neatness.

If your project demands fully dynamically generated base URLs then you can start reading this.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Mena
  • 3,019
  • 1
  • 25
  • 54
21

Also there is a such hack in Kotlin while defining base url

e.g.

@FormUrlEncoded
@Headers("Accept: application/json")
@POST
suspend fun login(
    baseUrl: String,
    @Field("login") login: String,
    @Field("password") password: String
    @Url url: String = "$baseUrl/auth"
): ResponseAuth

It's not working. Throws:

java.lang.IllegalArgumentException: No Retrofit annotation found. (parameter #1)

The only way is suggested by Jake Wharton https://github.com/square/retrofit/issues/2161#issuecomment-274204152

Retrofit.Builder()
    .baseUrl("https://localhost/")
    .create(ServerApi::class.java)
class DomainInterceptor : Interceptor {

    @Throws(Exception::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        return chain.proceed(
            request.newBuilder()
                .url(
                    request.url.toString()
                        .replace("localhost", "yourdomain.com:443")
                        .toHttpUrlOrNull() ?: request.url
                )
                // OR
                //.url(HttpUrl.parse(request.url().toString().replace("localhost", "yourdomain.com:443")) ?: request.url())
                .build()
        )
    }
}
Vlad
  • 7,997
  • 3
  • 56
  • 43
9

Build a new Retrofit client instance with the new URL

The easiest (but not the most performant) way to change the Retrofit base URL at runtime is to rebuild the retrofit client instance with the new URL:

private Retrofit retrofitInstance = Retrofit.Builder()
    .baseUrl(FlavourConstants.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .client(okHttpClient)
    .build();
    
public void setNewBaseUrl(String url) {
   retrofitInstance = new Retrofit.Builder()
      .baseUrl(url)
      .addConverterFactory(GsonConverterFactory.create(gson))
      .client(okHttpClient).build();
}

// ... then use this client instance
retrofitInstance.create(ApiService.class);

When using OkHttp, add an interceptor with the new URL

Alternatively, if you are using OkHttp with Retrofit, you can add an interceptor when building your OkHttp client.

Like in the example Gist swankjesse/HostSelectionInterceptor.java :

HostSelectionInterceptor hostInterceptor = new HostSelectionInterceptor();
hostInterceptor.setHost(newBaseUrl);

return new OkHttpClient.Builder()
    .addInterceptor(hostInterceptor)
    .build();
hc_dev
  • 8,389
  • 1
  • 26
  • 38
Phileo99
  • 5,581
  • 2
  • 46
  • 54
8

I just used the below function when i faced this problem. but i was on hurry and i believe that i have to use another and i was using "retrofit2:retrofit:2.0.2"

public static Retrofit getClient(String baseURL) {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseURL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        } else {
            if (!retrofit.baseUrl().equals(baseURL)) {
                retrofit = new Retrofit.Builder()
                        .baseUrl(baseURL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
            }
        }
        return retrofit;
    }

[Update] I have found this link that explain the @Url that can be sent as a parameter and i believe it is more professional than my old solution. Please find below the scenario:

    interface APIService{
         @POST
         Call<AuthenticationResponse> login(@Url String loginUrl,[other parameters])
    }

And below is the method in the class that provide the retrofit object

public static Retrofit getClient() {
    if (retrofit==null) {
        retrofit = new Retrofit.Builder()
                .baseUrl("http://baseurl.com") // example url
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    return retrofit;
}

Then you can call the method as below:

    APIInterface apiInterface = ApiClient.getClient2().create(ApiInterface.class);
    apiInterface.login("http://tempURL.com").enqueue(......);
Vattic
  • 301
  • 4
  • 9
1

Ok , if I dont remember wrong the docs of Retrofit says you can point to another URL if you just simply add in your interface servicse the full url of the ws, that is different fomr the BASE_URL in Retrofit Builder. One example...

public interface UserManager {  
    @GET("put here ur entire url of the service")
    public Call<ResponseBody> getSomeStuff();
}
Catluc
  • 1,775
  • 17
  • 25
1

You should use interceptor like this:

class HostSelectionInterceptor: Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        apiHost?.let { host ->
            val request = chain.request()
            val newUrl = request.url.newBuilder().host(host).build()
            val newRequest = request.newBuilder().url(newUrl).build()
            return chain.proceed(newRequest)
        }
        throw IOException("Unknown Server")
    }
}

You just need to change at runtime the apiHost variable (var apiHost = "example.com"). Then add this interceptor to OkHttpClient builder:

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(HostSelectionInterceptor())
    .build()
  • This is the best answer. It works well. Thank you! – Roshan Aug 11 '21 at 13:59
  • It's not great, because if you add HttpLogginInterceptor, it will always log the base url, you have intially provided and not new host. – Jokubas Trinkunas Nov 16 '22 at 08:07
  • Three issues with this answer: (1) Don't see where to change `apiHost` in your code example. (2) OP asks for Java, this solution is in Kotlin. (3) The _host-interceptor_ was [already answered by Phileo99](https://stackoverflow.com/a/41754823/5730279). – hc_dev May 12 '23 at 06:43
0

A solution is to have two distinct instance of retrofit, one for your FLAVOURED base URL and another for the other base URL.

So just define two functions :

     public Retrofit getFlavouredInstance() {
        return new Retrofit.Builder().baseUrl(FlavourConstants.BASE_URL).addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient).build();
     }

     public Retrofit getOtherBaseUrl() {
        return Retrofit.Builder().baseUrl(OTHER_BASE_URL).addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient).build();
     }

and after you just have to use the right one.

Mathieu Bertin
  • 1,634
  • 11
  • 11
0

Please try the following code:

private void modify(String url) throws Exception {

    Class mClass = retrofit.getClass();


    Field privateField = mClass.getDeclaredField("baseUrl");

    if (privateField != null) {
        privateField.setAccessible(true);

        System.out.println("Before Modify:MSG = " + retrofit.baseUrl().url().getHost());


        privateField.set(retrofit,  HttpUrl.parse(url));
        System.out.println("After Modify:MSG = " + retrofit.baseUrl().url().getHost());
    }
}
Tox
  • 834
  • 2
  • 12
  • 33
歐洺全
  • 21
  • 2
-1

You can regenerate the DaggerAppComponent after changing your apiUrl it will generate a new instance of providerRetrofit with the new url DaggerAppComponent.builder() .application(this) .build() Log.init( LogConfiguration .Builder() .tag("...") .logLevel(LogLevel.NONE) .build() )