Retrofit 2.0 tutorial with sample application

Retrofit is an HTTP client library in Android and Java. It has great performance compares to AsyncTask and other android networking libraries. Retrofit 2.0 has been released a few months back with a major update. In this tutorial, I am going to talk about how to create a simple REST client in Android that communicates with REST endpoint to perform the READ and WRITE operations. This tutorial is intended for retrofit 2.0 beginner who is using retrofit for the first time and getting their hand dirty with retrofit 2.0.

The overview of application that we are creating is as follows. The application has two main components 1) REST client in Android 2) REST server in Node.js. We will focus on REST client as this writing is for retrofit which is client library. We will create REST endpoints that create and list User. A user has two properties., username and name. The program  (Client) has two methods; one for creating a user and other for listing all the users. Final android application has two fragments in pager view, one has an interface for creating a new user and another for listing the user. In the backend, the program uses Mongodb as a database.

Getting Started

First of all, you need a couple of libraries to get started. Insert the following dependencies in your gradle file.
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'

As you can see, besides retrofit we are going to use gson converter. Gson convertor converts JSON into Java Object and vice-versa.  You can use another converter such as jackson if you like.

Model

Each user in the JSON maps to the User model. As the JSON contains the only username and name keys, User model has these two properties.

public class User {
public class User {
    public final String username;
    public final String name;
 
    public User(final String username, final String name) {
        this.username = username;
        this.name = name;
    }
}

API service

Service declaration converts HTTP API into Java interfaces.  This interface calls the HTTP web services. Retrofit 2.0 introduced a new class Call same as Okhttp. Call encapsulates single request/response interaction, it knows about serialization and de-serialisation. It takes the HTTP response and turns it into list of Users (in our case).  One good thing about Call is it actually separates request creation from response handling. Here is our implementation of service declaration
public interface APIService {
    @POST("/api/user")
    Call createUser(@Body User user);
 
    @GET("/api/user")
    Call> users();
}

The first call creates the POST request to the REST endpoint that creates new user according to the user object provided as a parameter. @Body denotes that the parameter provided after it acts as the request body of post request.  There are other kinds of annotations available to use as a parameter. For example, to create a dynamic URL, you could use @Path annotation. Here is the list of some of the annotations and their use cases.

@Query – Creates dynamic query string

Loading…
@GET("/api/user")
     Call getUser(Query("username") String username);
     // https://10.0.2.2:8080/api/user?username=bibek

This can be called in the following way – detail will be discussed later

Call service.getUser("bibek");

@QueryMap – Same as @Query except key and value can be supplied a map.

@GET("/api/user")
     Call getUser(QueryMap dynamic);
     // https://10.0.2.2:8080/api/user?username=bibek
     // Call service.getUser(Collections.singletonMap("username", "bibek"));

@Path – Dynamically replace the path

@GET("/api/user/{id}")
     Call getUser(@Path("id") String id);
     // https://10.0.2.2:8080/api/user/1
     // Call service.getUser(1);

@FormUrlEncoded – Sends form encoded data

@FormUrlEncoded
    @POST("/api/user")
    Call createUser(
        @Field("username") String username,
        @Field("name") String name
    );

Other annotations include @Header, @FieldMap, @Multipart, @Headers etc.

Rest Client

So far we created User model and declare the interface to communicate with HTTP using HTTP Verbs GET and POST. Now lets write code that calls these interfaces asynchronously.
public class RestClient{
 
    private static RestClient instance = null;
 
    private ResultReadyCallback callback;
 
    private static final String BASE_URL = "https://10.0.2.2:8080";
    private APIService service;
    List users = null;
    boolean success = false;
 
 
    public RestClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(BASE_URL)
                .build();
 
        service = retrofit.create(APIService.class);
    }
 
    public List getUsers() {
        Call> userlist = service.users();
        userlist.enqueue(new Callback>() {
            @Override
            public void onResponse(Response> response) {
                if (response.isSuccess()) {
                    users = response.body();
                    callback.resultReady(users);
                }
            }
 
            @Override
            public void onFailure(Throwable t) {
                  Log.e("REST", t.getMessage());
            }
        });
        return users;
    }
 
    public void setCallback(ResultReadyCallback callback) {
        this.callback = callback;
    }
 
    public boolean createUser(final Context ctx, User user) {
        Call u = service.createUser(user);
        u.enqueue(new Callback() {
            @Override
            public void onResponse(Response response) {
                success = response.isSuccess();
                if(success) {
                    Toast.makeText(ctx, "User Created", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(ctx, "Couldn't create user", Toast.LENGTH_SHORT).show();
                }
            }
 
            @Override
            public void onFailure(Throwable t) {
                Log.w("REST", t.getMessage());
                Toast.makeText(ctx, "Couldn't create user", Toast.LENGTH_SHORT).show();
 
            }
        });
        return success;
    }
 
    public static RestClient getInstance() {
        if(instance == null) {
            instance = new RestClient();
        }
        return instance;
    }
 
    public interface ResultReadyCallback {
        public void resultReady(List users);
    }
 
}

Here callbacks are used to make the code asynchronous. There is equivalent synchronous version for calling the interface methods but here I am using asynchronous methods because android doesn’t allows network operation in its main thread. Callback class has two methods onResponse and onFailure. If the request is successful, onResponse method is called with response encapsulated in Response type. Response class has methods that give main body and other various metadata related to the request and response. The Class looks like as follows

class Response {
        int code(); //returns HTTP code. e.g if success 200
        String message(); // message describing the code e.g if success OK
        Headers headers(); // HTTP headers
        boolean isSuccess(); // true if request status code is OK otherwise false
        T body(); // main body of response. It can be casted to desired model. 
                  // in our example it is casted to User type.
        ResponseBody errorBody(); // separate information about error
        com.squareup.okhttp.Response raw(); // Raw okhttp message
    }

Call.enqueue method enqueues the current request in asynchronous call queue. There is corresponding version in synchronous version Call.execute. I want to point out one possible exception when you try to execute or enqueue the request twice using same call object. For example

Call> call = service.users();
 
// execute
Response response = call.execute();
 
// try executing once again
Response response = call.execute(); // raise IlligalStateException
 
// Instead clone the call
Call> call2 = call.clone();
 
// and try to execute
Response response = call2.execute(); //works fine

Server Side

The server side code is written in Node.js. As this tutorial is for client part only. I simply show you the code that connects to mongodb, create user and save to the db and list all the user from db.

'use strict'
 
var express    = require('express'); 
var app        = express();                 
var bodyParser = require('body-parser');
var mongoose   = require('mongoose');
 
var Schema     = mongoose.Schema;
 
var UserSchema = new Schema({
    username: { type: String, required: true, index: { unique: true } },
    name: { type: String, required: true }
});
 
var User = mongoose.model('User', UserSchema);
mongoose.connect('mongodb://localhost/retrofit', function(err) {
 if(err) {
  console.log("Unable to connect to db :( : " + err)
 } else {
  console.log("Connected to database :)");
 }
});
 
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
 
var port = process.env.PORT || 8080; 
var router = express.Router();
 
router.get('/', function(req, res) {
    res.json({ message: 'Rest API for retrofit 2.0 application' });   
});
 
router.route('/user')
  .post(function(req, res) {
   var user = new User();
   user.username = req.body.username;
   user.name = req.body.name;
 
   user.save(function(user, err) {
    if (err)
                 res.send(err);
                else
              res.json({ message: 'User Created!' });
   });
  })
  .get(function(req, res) {
   var formattedUsers = [];
   User.find(function(err, users) {
             if (err)
                 res.send(err);
                else {
                 users.forEach(function(user) {
                  var formattedUser = {};
                  formattedUser.username = user.username;
                  formattedUser.name = user.name;
                  formattedUsers.push(formattedUser);
                 });
 
                 res.json(formattedUsers);
             } 
         });
  });
 
app.use('/api', router);
 
app.listen(port);

Now we came at the end of our tutorial. You can access complete code of both client and server in github repository.

Loading…