This page looks best with JavaScript enabled

Retrofit: extracting nested API objects as JavaBeans

 ·  ☕ 6 min read  ·  ✍️ Iskander Samatov

This tutorial is about the basics of Retrofit library and solving the problem of extracting nested objects from the API calls as JavaBeans .

Retrofit is a great library from Square that let’s you structure your api calls as declarative, type-safe callable interfaces.

Even though their docs are great and more than enough to start using the library, solving more complicated problems requires more digging. It took me some time and searching through stackoverflow to figure out a way to extract nested objects from API responses so I hope this article will save you some of that time.

Dependencies

This is the list of the dependencies needed for this tutorial:

  • implementation 'com.squareup.retrofit2:retrofit:2.4.0'
  • implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
  • implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
  • implementation 'com.squareup.okhttp3:okhttp:3.12.0'

API call structure

In this tutorial I’m going to make calls to my back-end API and extract user data.

This is what my user data call result looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
     "ok": true,
     "result": {
         "user": {
             "draftRefs": {},
             "favorite": {
                 "FeNEroVtDEVf07t6Xo2a": {
                     "author": "bob",
                     "title": "Lonely Wolf"
                 },
                 "DKNlu9fYdGZHmLqjmM2j": {
                     "author": "iskenxan",
                     "title": "In the dark"
                 },
                 "3CRdx1Q2t61wGMWIukkv": {
                     "title": "In the dark",
                     "author": "iskenxan"
                 }
             },
             "following": {
                 "bob": {
                     "profileUrl": ""
                 },
                 "iskander": {
                     "profileUrl": ""
                 },
                 "ashleyisawesome": {
                     "profileUrl": ""
                 },
                 "iskenxan": {
                     "profileUrl": "https://firebasestorage.googleapis.com/v0/b/travelguide-bf6df.appspot.com/o/iskenxan.jpg?alt=media"
                 }
             },
             "publishedRefs": {},
             "profileUrl": null,
             "followers": {
                 "iskenxan": {
                     "profileUrl": "https://firebasestorage.googleapis.com/v0/b/travelguide-bf6df.appspot.com/o/iskenxan.jpg?alt=media"
                 },
                 "iskander": {
                     "profileUrl": ""
                 }
             },
             "username": "iskander"
         },
         "token": "SECURITY_TOKEN"
     }
 }

The part that we’re trying to retrieve from the result is nested under result json.

This is what my JavaBean looks like. As you can see the structure and the variable names must reflect result json.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class User {
    private String username;
    private String profileUrl;
    private Map followers; //username, picture_url
    private Map following; //username, picture_url
    private Map draftRefs; //draftId, draft data
    private Map publishedRefs; //postId, draft data

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }


    public Map getDraftRefs() {
        return draftRefs;
    }

    public void setDraftRefs(Map draftRefs) {
        this.draftRefs = draftRefs;
    }

    public Map getPublishedRefs() {
        return publishedRefs;
    }

    public void setPublishedRefs(Map publishedRefs) {
        this.publishedRefs = publishedRefs;
    }

    public String getProfileUrl() {
        return profileUrl;
    }

    public void setProfileUrl(String profileUrl) {
        this.profileUrl = profileUrl;
    }


    public Map getFollowers() {
        return followers;
    }

    public void setFollowers(Map followers) {
        this.followers = followers;
    }

    public Map getFollowing() {
        return following;
    }

    public void setFollowing(Map following) {
        this.following = following;
    }
}

Implementation

When starting a new project with Retrofit I like to split the logic in three interdependent parts:

  • Retrofit client builder — Responsible for creating new instances of the Retrofit client
  • Custom Deserializer — Extracts nested objects from the response
  • Call interfaces — Interfaces to defines API calls
  1. Client Builder

This what RetrofitClientBuilder looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ApiManager {

    private static String BASE_URL = "YOUR_BASE_URL";


    public static Retrofit getRetrofit() {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .readTimeout(10, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build();

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


    private static Gson getConverter() {
        return new GsonBuilder()
                        .registerTypeAdapter(User.class, new MyDeserializer<user>())
                        .create();
    }
}
  • BASE_URL – Base url for all the endpoint calls
  • getRetrofit() – Initiates the Retrofit client instance. It uses OkHttp library to make actual requests. When building OkHttp instance we set a timeout for 10 seconds, you can change that value of course. To convert server response from json to JavaBean we use an instance of Gson which is returned from getConverter() method
  • getConverter() – Returns an new instance of the Gson class with type adapters we register to use our JavaBeans. As you can see we register User JavaBean we defined, but you can also register primitives like String.
  1. Deserializer

Next we define MyDeserializer class we used in getConverter method. It’s responsible for extracting the nested result object from our json response.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyDeserializer<t> implements JsonDeserializer<t> {
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get("result");

        T result =  new Gson().fromJson(content, type);

        return result;
    }
}

MyDeserializer implements JsonDeserializer<T> interface where T stands for a generic type. Using this generic implementation we’re able to reuse MyDeserializer for multiple different JavaBeans. This interface has only one deserialize method that we need to override. je variable is JsonElement created from our server response json, so it has the same structure. We extract the nested result object from je and create an instance of our JavaBean object using Gson and return it.

  1. Callable Interfaces

In callable interfaces we declaratively define the structure of our REST calls. Here’s what they look like:

1
2
3
4
 public interface UserApi {
    @POST("user/profile/me") 
    Call<user> getUserInfo(@Body HashMap<string, object=""> params);
 }

@POST defines the request method (which is post, duh) and the url address of the endpoint which will be combined with BASE_URL we defined earlier to build full http path to your endpoint. @Body defines the body of the request. It doesn’t have to be HashMap you can use other objects defined in Retrofit docs .

Usage

Here’s the code for making a request on a separate thread using Thread objects. I don’t advice using this method, I used it to keep this tutorial short. I advice using AsyncTask or RxJava library for handling asynchronous tasks in Android:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Thread thread = new Thread(() -> {
    try {
        String token = "YOUR_BACKEND_AUTH_CODE";
        ProfileApi api = ApiManager.getRetrofit().create(ProfileApi.class);
    HashMap<string, object=""> params = new HashMap<>();         
    params.put("token", token);             
    User user = api.getUserInfo(params).execute().body();             
    user = user;             
    runOnUiThread(() -> {                 
            // use user object to update the app in the ui thread             
        });         
        } catch (IOException e) {
            e.printStackTrace();         
        }     
    });     
    thread.start();
  • We create an instance of our callable interface using ApiManager.getRetrofit() we defined earlier
  • We define parameters for our post request using params hashmap
  • Finally we get an instance of the Call object using getUserInfo method we defined in our interface and call execute and body methods to execute and get the body of the response.
  • If everything is working correctly body method should return User populated with the fields from the result nested object.

And that’s the end of this tutorial, hopefully you were able to follow through and get your Retrofit setup working. Retrofit is a great library and a must have for any Android developer who wishes to keep his/her code clean and maintainable.

If you’d like to get more web development, React and TypeScript tips consider following me on Twitter, where I share things as I learn them.
Happy coding!

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on React, JavaScript and web development.