In this tutorial we'll show you a very handy tool, which can make your debugging life so much easier. If you're an Android app developer and you've to deal with multiple versions of an API (for example develop, staging and production) you're probably tired of building three (or more) versions of the app, if you quickly want to see if your app works against all server deployments.
In the next few minutes, you'll learn how you can change the API base url at runtime! This means, you can check with one compilation and installation how your app behaves with all API versions.
In the last few months, we've published a variety of Retrofit tutorials:
Retrofit Series Overview
- Getting Started and Creating an Android Client
- Basics of API Description
- Creating a Sustainable Android Client
- URL Handling, Resolution and Parsing
- How to Change API Base Url at Runtime
- Multiple Server Environments (Develop, Staging, Production)
- Share OkHttp Client and Converters between Retrofit Instances
- Upgrade Guide from 1.9
- Beyond Android: Retrofit for Java Projects
- How to use OkHttp 3 with Retrofit 1
- Synchronous and Asynchronous Requests
- Send Objects in Request Body
- Add Custom Request Header
- Manage Request Headers in OkHttp Interceptor
- Dynamic Request Headers with @HeaderMap
- Multiple Query Parameters of Same Name
- Optional Query Parameters
- Send Data Form-Urlencoded
- Send Data Form-Urlencoded Using FieldMap
- How to Add Query Parameters to Every Request
- Add Multiple Query Parameter With QueryMap
- How to Use Dynamic Urls for Requests
- Constant, Default and Logic Values for POST and PUT Requests
- Cancel Requests
- Reuse and Analyze Requests
- Optional Path Parameters
- How to Send Plain Text Request Body
- Customize Network Timeouts
- How to Trust Unsafe SSL certificates (Self-signed, Expired)
- Dynamic Endpoint-Dependent Interceptor Actions
- How to Update Objects on the Server (PUT vs. PATCH)
- How to Delete Objects on the Server
- Introduction to (Multiple) Converters
- Adding & Customizing the Gson Converter
- Implementing Custom Converters
- How to Integrate XML Converter
- Access Mapped Objects and Raw Response Payload
- Supporting JSON and XML Responses Concurrently
- Handling of Empty Server Responses with Custom Converter
- Send JSON Requests and Receive XML Responses (or vice versa)
- Unwrapping Envelope Responses with Custom Converter
- Wrapping Requests in Envelope with Custom Converter
- Define a Custom Response Converter
The Core: ServiceGenerator
Long time readers will know most of our Retrofit tutorials utilize our ServiceGenerator
class from the creating a sustainable android client tutorial. Since we'll mainly work in the ServiceGenerator
code, make sure you're familiar with this class.
In the current version the ServiceGenerator
works with multiple static fields and a String constant API_BASE_URL
, which holds the API base url:
public class ServiceGenerator {
public static final String API_BASE_URL = "http://futurestud.io/api";
private static Retrofit retrofit;
private static Retrofit.Builder builder =
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(API_BASE_URL);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
// No need to instantiate this class.
private ServiceGenerator() {
}
public static <S> S createService(Class<S> serviceClass, AccessToken token) {
String authToken = token.getTokenType().concat(token.getAccessToken());
return createService(serviceClass, authToken);
}
// more methods
// ...
}
Adjusting the ServiceGenerator
With this setup you don't have a chance to change the API_BASE_URL
constant at runtime. You've change it in the source code, compile a new .apk
and test it again. Since this is very inconvenient if you're working with multiple API deployments, we'll make minor changes to the ServiceGenerator
class:
public class ServiceGenerator {
public static String apiBaseUrl = "http://futurestud.io/api";
private static Retrofit retrofit;
private static Retrofit.Builder builder =
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(apiBaseUrl);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
// No need to instantiate this class.
private ServiceGenerator() {
}
public static void changeApiBaseUrl(String newApiBaseUrl) {
apiBaseUrl = newApiBaseUrl;
builder = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(apiBaseUrl);
}
public static <S> S createService(Class<S> serviceClass, AccessToken token) {
String authToken = token.getTokenType().concat(token.getAccessToken());
return createService(serviceClass, authToken);
}
// more methods
// ...
}
Let's examine our changes. We've renamed the constant API_BASE_URL
to a non-final field apiBaseUrl
. We also added a new static method changeApiBaseUrl(String newApiBaseUrl)
, which will change that particlar apiBaseUrl
variable. It also creates a new version of the Retrofit.Builder
instance builder
. This is important because we're re-using the builder
for requests. If we don't create a new instance all requests still would have gone against the original apiBaseUrl
value.
Example Usage
We've made the necessary changes to the ServiceGenerator
. Let's move on to actually utilize our new functioanlity:
public class DynamicBaseUrlActivity extends AppCompatActivity {
public static final String TAG = "CallInstances";
private Callback<ResponseBody> downloadCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_upload);
downloadCallback = new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.d(TAG, "server contacted at: " + call.request().url());
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d(TAG, "call failed against the url: " + call.request().url());
}
};
// first request
FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);
Call<ResponseBody> originalCall = downloadService.downloadFileWithFixedUrl();
originalCall.enqueue(downloadCallback);
// change base url
ServiceGenerator.changeApiBaseUrl("http://development.futurestud.io/api");
// new request against new base url
FileDownloadService newDownloadService = ServiceGenerator.create(FileDownloadService.class);
Call<ResponseBody> newCall = newDownloadService.downloadFileWithFixedUrl();
newCall.enqueue(downloadCallback);
}
}
In this activity we're showing you two requests to download a file from the server. If you're interested in the details on how to download files with Retrofit, head over to the relevant tutorial. After the first request is executed, we change the base url to our development environment with the new ServiceGenerator.changeApiBaseUrl()
method. Lastly, we'll make the same download request again. When we start the app, we're getting the following logs:
D/CallInstances: server contacted at: http://futurestud.io/resource/example.zip
D/CallInstances: server contacted at: http://development.futurestud.io/resource/example.zip
This is exactly as we wanted Retrofit to behave. The first request still goes against the production server. The second request, after changing the base url, goes against our development server. Awesome!
When To Change the Base Url at Runtime
The demo code above simplifies things a little. We usually implement a button in the debug version of the app, where the tester can select the wished server environment. Thus, depending on your situation, you probably have to write a bit more code to decide when and to which base url Retrofit should change to.
Also, we only recommend doing this for debugging purposes. We don't think this is a good way of making your app work with various servers at the same time. If your app needs to deal with more than one API, look for a different version. The dynamic url tutorial might be a good start.
Lastly, please test if you can simply switch environments with your app. For example, if you store user and authentication information on the server side, switching the environment might cause problems. Your production database most likely does not contain the same users as your development database, correct? In our apps, we delete all relevant user data and force a new, fresh login after the tester changed the environment via a new base url.
Summary
In this tutorial, you've learned how to change the API base url at runtime. This can be incredibly useful if you're dealing with multiple API deployments. We've shown you the necessary enhancement to the ServiceGenerator
class and how to make the necessary requests. You also have to be aware of the possible consequences when switching API deployments during runtime. Nevertheless, if you spent the hour to implement this for your app, you'll save days of compiling time!
If you think is useful or if you've any questions, let us know in the comments!