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
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
public interface APIService { @POST("/api/user") Call<User> createUser(@Body User user); @GET("/api/user") Call<List<User>> 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
@GET("/api/user") Call<User> getUser(Query("username") String username); // http://10.0.2.2:8080/api/user?username=bibek
This can be called in the following way – detail will be discussed later
Call<User;> service.getUser("bibek");
@QueryMap – Same as @Query except key and value can be supplied a map.
@GET("/api/user") Call<User> getUser(QueryMap<string, String> dynamic); // http://10.0.2.2:8080/api/user?username=bibek // Call<User> service.getUser(Collections.singletonMap("username", "bibek"));
@Path – Dynamically replace the path
@GET("/api/user/{id}") Call<User> getUser(@Path("id") String id); // http://10.0.2.2:8080/api/user/1 // Call<User> service.getUser(1);
@FormUrlEncoded – Sends form encoded data
@FormUrlEncoded @POST("/api/user") Call<user> createUser( @Field("username") String username, @Field("name") String name ); </user>
Other annotations include @Header, @FieldMap, @Multipart, @Headers etc.
Rest Client
public class RestClient{ private static RestClient instance = null; private ResultReadyCallback callback; private static final String BASE_URL = "http://10.0.2.2:8080"; private APIService service; List<User> 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<User> getUsers() { Call<List<User>> userlist = service.users(); userlist.enqueue(new Callback<List<User>>() { @Override public void onResponse(Response<List<User>> 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<User> u = service.createUser(user); u.enqueue(new Callback<User>() { @Override public void onResponse(Response<User> 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<User> 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<T> { 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<List<User>> call = service.users(); // execute Response response = call.execute(); // try executing once again Response response = call.execute(); // raise IlligalStateException // Instead clone the call Call<List<User>> 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.
sahi ho k .. keto le garcha 🙂 (y)
I am getting error at callback.resultReady(shops);
as follows
java.lang.NullPointerException: Attempt to invoke interface method 'void com.bookhaircut.externals.RestClient$ResultReadyCallback.resultReady(java.util.List)' on a null object reference
Line 38 in RestClient at return users;
users = null
can you help ?
This is a asynchronous call, so expect a result in a "synchronous" way is not correct. You can take a look to this: http://stackoverflow.com/questions/34184088/how-can-i-return-value-from-function-onresponse-of-retrofit
Youa are probably not assignin the callback anywhere