Retrofit 2.0 (Android tutorial)

Retrofit is one great lib to handle REST API calls in Android apps. Its 2.0 beta 1 version came out not too long ago, and I had to implement a small project which used it, so I figured “why not show the world my breakthroughs?” (Yeah, right). Here it goes.

Before starting, make sure you add these dependencies to your gradle.build file:

compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'

For this tutorial (is it a tutorial, though?) I’ll use OpenWeatherMap’s cool API, which is very flexible and allows you to access the current weather information for any place in the world.

The first thing we’ll need to create is a model class, so we can map the response to something and access its values easily. Let’s call it WeatherInfo, as it will hold the the information for a given place. But wait! We don’t know yet what a response will look like. If we issue a GET request to api.openweathermap.org/data/2.5/weather?q=London, we get something like this:


{
"coord": {
"lon": -0.13,
"lat": 51.51
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "Sky is Clear",
"icon": "01d"
}
],
"base": "cmc stations",
"main": {
"temp": 290.12,
"pressure": 1028,
"humidity": 45,
"temp_min": 289.15,
"temp_max": 291.48
},
"wind": {
"speed": 3.1,
"deg": 90
},
"clouds": {
"all": 0
},
"dt": 1443282691,
"sys": {
"type": 1,
"id": 5091,
"message": 0.0036,
"country": "GB",
"sunrise": 1443246807,
"sunset": 1443289743
},
"id": 2643743,
"name": "London",
"cod": 200
}

To keep it simple, let’s focus only on the weather and name fields of the response. Our WeatherInfo POJO will look like this:


package com.mycompany.myfirstapp.model.weather;
public class WeatherInfo {
private Weather[] weather;
private String name;
public Weather[] getWeather() {
return weather;
}
public void setWeather(Weather[] weather) {
this.weather = weather;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

As you might have noticed, weather is actually an array of objects, so we have to create a Weather class, which in turn holds a description attribute. Here it is!


package com.mycompany.myfirstapp.model.weather;
public class Weather {
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

view raw

Weather.java

hosted with ❤ by GitHub

Now that our model structure is complete, let’s move to our second step.

The next thing we’ll need is an interface declaring the methods we’ll use. These methods must return a Call instance, which will be useful later, when we handle the actual request.

Let’s say we want to make 2 types of requests: one to get a place’s weather by its name, and another to get it with its coordinates. This is the way we’d do it with Retrofit:


package com.mycompany.myfirstapp.api;
import com.mycompany.myfirstapp.model.weather.WeatherInfo;
import retrofit.Call;
import retrofit.http.GET;
import retrofit.http.Query;
public interface WeatherInfoApi {
@GET("/data/2.5/weather")
Call<WeatherInfo> getWeatherForName(@Query("q") String name);
@GET("/data/2.5/weather")
Call<WeatherInfo> getWeatherForCoords(@Query("lat") double lat, @Query("lon") double lon);
}

Notice we don’t actually implement the methods, just declare them. Retrofit will use the URL provided in the @GET annotation, and the parameters specified using @Query to actually implement the methods’ logic.

Now, let’s define a service, which will handle Retrofit’s configuration and will expose an API for other components (like Activities) to use. It’s actually a very simple class:


package com.mycompany.myfirstapp.service;
import com.mycompany.myfirstapp.api.WeatherInfoApi;
import java.io.IOException;
import retrofit.Callback;
import retrofit.GsonConverterFactory;
import retrofit.Retrofit;
public class WeatherInfoService {
private WeatherInfoApi weatherInfoApi;
public WeatherInfoService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.openweathermap.org&quot;)
.addConverterFactory(GsonConverterFactory.create())
.build();
weatherInfoApi = retrofit.create(WeatherInfoApi.class);
}
public void getWeatherForName(String name, Callback callback) throws IOException {
weatherInfoApi.getWeatherForName(name).enqueue(callback);
}
public void getWeatherForCoords(double lat, double lon, Callback callback) throws IOException {
weatherInfoApi.getWeatherForCoords(lat, lon).enqueue(callback);
}
}

The constructor takes care of configuring Retrofit, by:

  • Setting the requests’ base URL (It really has to be the base URL. Believe me. It will remove all trailing paths, like /data/2.0).
  • Setting the library it’ll use to convert the responses’ body into our model structure (This is why we had to include Retrofit’s GSON converter in our dependencies). Another popular choice is Jackson, but GSON is just fine for us.

This is where Retrofit’s magic happens. Once it’s configured, we can create an instance of our WeatherInfoApi interface, allowing us to use its methods and make requests to the endpoints we set up.

Notice the methods receive a Callback as a parameter. This will allow the services and activities which use our service to specify tasks they want to be executed when the response arrives. They are queued to be executed asynchronously, so that the calling thread doesn’t get blocked, avoiding causing a weird experience for the user. We’ll see this more clearly next.

We’ll create an Activity that uses our service’s methods to get the weather with a place’s name or coordinates. The name string and coordinates can be obtained in various ways, like text fields or a map (a cooler way, IMHO). I won’t go into that part, though, so I’ll leave it up to your imagination! Take a look at our Activity’s class:


package com.mycompany.myfirstapp.activity.weather;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.io.IOException;
import com.mycompany.myfirstapp.service.WeatherInfoService;
import com.mycompany.myfirstapp.model.weather.WeatherInfo;
import retrofit.Callback;
import retrofit.Response;
public class WeatherActivity extends AppCompatActivity {
// This will hold our WeatherInfoService instance.
private WeatherInfoService weatherInfoService;
@Override
protected void onCreate(Bundle savedInstanceState) {
// onCreate logic…
// Initialize our service instance.
weatherInfoService = new WeatherInfoService();
}
private void getWeatherForName(String name) {
try {
weatherInfoService.getWeatherForName(name, new Callback() {
// This is executed when the request goes well 🙂
@Override
public void onResponse(Response response) {
// response.body holds the response, mapped to our model.
WeatherInfo weatherInfo = (WeatherInfo) response.body();
// Do something with weatherInfo, like
// String description = weatherInfo.getWeather()[0].getDescription();
// which will be something like "Sky is Clear".
}
// This one's called when it goes bad 😦
@Override
public void onFailure(Throwable t) {
// Tell the user there was an error.
}
});
} catch (IOException e) {
// Recover from failure.
}
}
private void getWeatherForCoords(double lat, double lon) {
try {
weatherInfoService.getWeatherForCoords(lat, lon, new Callback() {
@Override
public void onResponse(Response response) {
// here, too, response.body holds the response, mapped to our model.
WeatherInfo weatherInfo = (WeatherInfo) response.body();
// Do something with it. Or not. But that would be weird.
}
@Override
public void onFailure(Throwable t) {
// Tell the user something went wrong.
}
});
} catch (IOException e) {
// Recover from failure!
}
}
}

If you’ve ever done something in front-end Javascript, you’ll find these Callbacks are actually very much like promises, and they allow the Activity to handle how it will react if the requests go well or bad. Retrofit uses the GSON converter to map the response’s body to the model we defined, so now we can use its values however we want!

I hope this helps while Retrofit updates their documentation (they erased their 1.x docs and uploaded unfinished ones for 2.x -__-). If you have any questions or suggestions, I’d be happy to hear them!