Retrofit offers a wide range of functionalities and there are a lot of possible configurations. A lot of larger applications will require some specific setup, for example for OAuth authentication. In order to achieve a clean and stable project, we'll introduce you our idea of a sustainable Android client: the ServiceGenerator
.
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 ServiceGenerator
As you know from our getting started with Retrofit tutorial, the Retrofit
object and its builder are the heart of all requests. Here you configure and prepare your requests, responses, authentication, logging and error handling. Unfortunately, we've seen too many developers just copy-and-pasting these parts instead of separating into one clean class. The ServiceGenerator
will give you our solution, which is based on Bart Kiers' idea.
Let's start with the simple code. In its current state, it only defines one method to create a basic REST client for a given class/interface, which returns a service class from the interface.
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
public static <S> S createService(
Class<S> serviceClass) {
return retrofit.create(serviceClass);
}
}
The ServiceGenerator
class uses Retrofit’s Retrofit
builder to create a new REST client with the given API base url (BASE_URL
). For example, GitHub’s API base url is located at https://api.github.com/
and you must update the provided example url with your own url to call your API instead of GitHub’s.
The createService
method takes a serviceClass
, which is the annotated interface for API requests, as a parameter and creates a usable client from it. On the resulting client you'll be able to execute your network requests.
Why Is Everything Declared Static Within the ServiceGenerator
?
You might wonder why we use static fields and methods within the ServiceGenerator
class. Actually, it has one simple reason: we want to use the same objects (OkHttpClient
, Retrofit
, …) throughout the app to just open one socket connection that handles all the request and responses including caching and many more. It’s common practice to just use one OkHttpClient
instance to reuse open socket connections. That means, we either need to inject the OkHttpClient
to this class via dependency injection or use a static field. As you can see, we chose to use the static field. And because we use the OkHttpClient
throughout this class, we need to make all fields and methods static.
Additionally to speeding things up, we can save a little bit of valuable memory on mobile devices when we don't have to recreate the same objects over and over again.
Using the ServiceGenerator
Remember how our code looked in the getting started with Retrofit tutorial?
String API_BASE_URL = "https://api.github.com/";
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(
GsonConverterFactory.create()
);
Retrofit retrofit =
builder
.client(
httpClient.build()
)
.build();
GitHubClient client = retrofit.create(GitHubClient.class);
For one request, this looks fine. But if you have dozens of network requests throughout your app, it'll be a nightmare to manage. With our ServiceGenerator
, you only need a single line:
GitHubClient client = ServiceGenerator.createService(GitHubClient.class);
All of the preparations were moved into our ServiceGenerator
.
Unfortunately, in most cases the ServiceGenerator
cannot stay this simple. Thus, the code from above only gives you a starting point. You'll need to adapt it to your needs just like we'll do in other tutorials. Nevertheless, in the next two sections we'll explore a few possible changes.
Preparing Logging
One of the most common wishes for developers is to know what kind of data is actually being send and received by Retrofit. We have an entire tutorial dedicated on logging with Retrofit where you can learn more.
Logging with Retrofit 2 is done by an interceptor called HttpLoggingInterceptor
. You'll need to add an instance of this interceptor to the OkHttpClient
. For example, you could solve it the following way:
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
public static <S> S createService(
Class<S> serviceClass) {
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}
}
There are a few things you've to be aware of. Firstly, make sure you're not accidentally adding the interceptor multiple times! We check with httpClient.interceptors().contains(logging)
if the logging interceptor is already present. Secondly, make sure to not build the retrofit
object on every createService
call. Otherwise the entire purpose of the ServiceGenerator
is defeated.
Prepare Authentication
The requirements for authentication are a little bit different. You can learn more in our tutorials on Basic Authentication, Token Authentication, OAuth, or even Hawk Authentication. While the details are a little different for each authentication implementation, you probably will have to change the ServiceGenerator
. One of the changes is that you need to pass additional parameters to createService
to create a client.
Let's look at an example for Hawk authentication:
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
public static <S> S createService(
Class<S> serviceClass, final HawkCredentials credentials) {
if (credentials != null) {
HawkAuthenticationInterceptor interceptor =
new HawkAuthenticationInterceptor(credentials);
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor);
builder.client(httpClient.build());
retrofit = builder.build();
}
}
return retrofit.create(serviceClass);
}
}
Our createService
now has a second parameter for the HawkCredentials
. If you pass a non-null value, it'll create the necessary Hawk authentication interceptor and add it to the Retrofit client. We also need to rebuild Retrofit to apply our changes to the next request.
One more heads-up, you probably will see slightly different versions of the ServiceGenerator
in the other tutorials. Don't be confused! We recommend that you also keep your ServiceGenerator
slim and specialized for the use case!
What Comes Next
In this tutorial you've learned why centralizing your Retrofit client generation makes sense and is recommended. You've seen one approach how you can implement it with the ServiceGenerator
class. Nevertheless, you probably will have to adjust it to your purposes.
If you've feedback or a question, let us know in the comments or on twitter @futurestud_io.
Make it rock & enjoy coding!