Mobilization advanced retrofit puchalski header

Advanced Retrofit

The Retrofit library makes developers’ life much easier. It serves a very important need these days - to connect to the Internet and get data from it. Retrofit takes cared of this in a simplified way that’s pared down to just the minimum required code. In this talk from Mobilization Conference, Maciej Puchalski describes advanced concepts, along with a little introduction to the library.


What is Retrofit?

Retrofit is a library made for a world in which devices need a constant connection to data. It’s an open-source HTTP client for Android developed by Square.

In this talk, I will show you some code without Retrofit, some Retrofit basics, and handpicked advanced examples and real problems.

Endpoint Code

Here is a basic GET request.


String url =
"https://api.github.com/users/sennajavie/repos”;

URL obj = new URL(url);

HttpURLConnection con =
  (HttpURLConnection) obj.openConnection();

con.setRequestMethod("GET");



final HashMap<String, String> requestParams =
  new HashMap<>();

requestParams.put("User-Agent","Mozilla/5.0");
requestParams.put("Content-Type","message");



BufferedReader in = new BufferedReader(...);
String data;
StringBuffer response = new StringBuffer();

while ((data = in.readLine()) != null) {
    response.append(data);
}

The above is a GET method to GitHub with some request parameters. Here, we read this data in a single string without a model. And lastly, we have to close our connections and buffered readers.

Enter Retrofit

Retrofit allows us to scrap the above code, and replace it with only three lines to accomplish the same task.

Get more development news like this

interface ApiClient {

@GET("users/{user}/repos")
Call<List<Repository>>
  getReposList(@Path("user") String user);

This is an API client with a GET method that downloads some repositories from GitHub. We specify the user by injecting it into the URL. The most interesting part here is the model. We have a concrete model - a concrete list of readable repositories that have been already converted from the string that received from the API.

Converters

Retrofit knows to replace the string of data into a list of repositories through Converters.

Converters serialize and deserialize data for a readable result, or in case of sending it back, we just make it more readable for the API and HTTP to do its work.

If the models are based on something other than serialization/deserialization, this will not be an issue, as Retrofit has many other built-in converters. The more popular ones are available out of the box: GSON, Jackson, Moshi, Protobuf, Wire, simple XML; the most popular ones and those that are available out of the box.

At our company, we had an API that required passing in the User to delete a user from a database. This wasn’t pretty, but Retrofit allows us this workaround:


interface ApiClient {

@HTTP(method = "DELETE",
      path = "users/{user}/repos",
      hasBody = true)
Call<ResponseBody>
  deleteRepo(@Body Repository repo,
            @Path("user") String user);

}

We have our DELETE method, but we supply it with a body. Usually, it is illegal to provide a delete method with the body, however, Retrofit allows us to get it working. We just specify our HTTP method, name it “DELETE”, then specify it needs to have the body.

The rest of it is quite simple: we supply the path, and we supply the body.

The magic comes from the body parameter. Here’s when the converter applies its magic to serialize repository into a list of characters at JSON.

We also have something like formUrlencoded.


interface ApiClient {

  @FormUrlEncoded
  @POST("users/newUser")
  Call<ResponseBody>
    postNewUser(@Field("name") String name,
                @Field("mail") String mail,
                ...);

}


interface ApiClient {

  @FormUrlEncoded
  @POST("users/newUser")
  Call<ResponseBody>
    postNewUser(@Body User user,
                @Field("timestamp") long date,
                @Header("sys") String system);

}

Here, provide the API with some data, and it’s posted into API in the format: name, value, name, value. If we want to get different pairs of this data, we could use field map and then supply a simple Java HashMap with strings.

We can also mix those solutions.

In the above, we have an URL which posts new user. This endpoint needs some additional fields, not only the user, as part of the body. This is allowed, as Retrofit supports it.

ApiClient Creation

Here is the initialization of the ApiClient.

interface ApiClient {

@GET
  Call<List<Repository>>
    getReposList(@Url String url,
                  @Path("user") String user);
}



gson = GsonConverterFactory.create();

Retrofit retrofit = new Retrofit.Builder()
          .addConverterFactory(gson)
      .baseUrl("https://api.github.com/")
      .build();

return retrofit.create(ApiClient.class);

Here, we use a GSON converter and apply it to the Retrofit builder, then apply the base URL. The most interesting part is the final line, it’s when we throw out our stack of code and Retrofit supplies it with its own stack of code. Everything is analyzed based on our needs that we provided with annotations (e.g. get, buff).

Client - Request Modify

Regarding Retrofit configuration, imagine such a situation in which our Facebook API having hundreds of endpoints. Each endpoint has some form of authentication.


@Override
public okhttp3.Response intercept(Chain chain)
{

  final Request request =
          chain.request().newBuilder()
          .addHeader("Authorization", "CODE")
          .build();

  return chain.proceed(request);

}


final OkHttpClient client =
  new OkHttpClient.Builder()
  .addInterceptor(new AuthHeaderInterceptor())
  .build();

//..
retrofitBuilder.client(client)

//..
return retrofit.create(ApiClient.class);


The smart way to handle this is to intercept every request from Facebook to apply our authentication token dynamically.

For example, we will be sending a request to load an image. The request will be intercepted and the above code will invoke. It will add a header everything proceeds as normal.

To apply this, add the intercept and create an Ok HTTP client with it.

Debugging

A next cool thing with interceptors is that it allows for debugging. Using class HTTP logging interceptor, we specify what interests us.


HttpLoggingInterceptor loggingInterceptor =
  new HttpLoggingInterceptor();

loggingInterceptor.setLevel(
  HttpLoggingInterceptor.Level.BODY);

In this example, we have level body: we get everything debugged, written down in our console windows.

Sequence matters!

Logger knows nothing about the authentication token because the sequence matters.

The proper course of action would be to have everything intercepted and added before logging.

OkHttpClient.Builder()
  .addInterceptor(authHeaderInterceptor)
  .addInterceptor(loggingInterceptor)

RxJava + Retrofit

We will extend our Retrofit with slight bits of RxJava. It’s a functional approach that will work its magic. You don’t need to have full-blown, functional logic in your code. We will use just simple RxJava to make Retrofit work better.


Retrofit retrofit =
  new Retrofit.Builder()
  //...
  .addCallAdapterFactory(
  RxJavaCallAdapterFactory.create())
  //...
  .build();


interface ApiClient {

  @GET("users/{user}/repos")
  Observable<List<Repository>>
    getReposList(@Path("user") String user);

}

We got the repositories observable on call. We add some two lines of code and subscribe. The magic happens when we do the subscribe and inside subscriber, it waits for our data to come.


reposObservable =
  apiClient.getReposList("sennajavie");

reposObservable
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
      .subscribe((repos) -> {
              repos.get(0);
          });


RxJava - Threads


reposObservable.
  subscribeOn(Schedulers.io())

reposObservable.
  observeOn(AndroidSchedulers.mainThread())


If we execute the networking on main thread, it will get us an exception on Android. To avoid this, specify the work to be done in the background thread.

RxJava - Stopping threads

Imagine that our client has a problem with their app. The app gets launched, and something loads. Then the client gets irritated because it takes too long to load. The client shuts the app down. RxJava allows us to suspend, cancel, any of our request that we’ve made during the time and at the precise moment.


CompositeSubscription foregroundSub;

public void appStarted() {
    foregroundSub = new CompositeSubscription();
}

public void appStopped() {
    foregroundSub.unsubscribe();
}


Subscriber<List<Repository>> reposSubscriber =
  (repos) -> {
    repos.get(0);
  };

foregroundSub.add(reposSubscriber);
//…

reposObservable.subscribe(reposSubscriber);

How does composite subscription work? Unfortunately, we have to remember that in each callback subscriber we create, we also have to place it in the foreground subscription.

Summary

Retrofit provides us with less code and allows for more possibilities. Those possibilities are connected to the endpoint control. We have granular endpoint control, and we get RxJava support out of the box.

Next Up: Crafting Reactive Apps with Realm Mobile Platform

General link arrow white

About the content

This talk was delivered live in October 2017 at Mobilization. The video was transcribed by Realm and is published here with the permission of the conference organizers and speakers.

Maciej Puchalski

Maciej is and enthusiast of mobile programming, especially on the Android platform. He is currently a developer at SoftwareHut.

4 design patterns for a RESTless mobile integration »

close