What Is Retrofit?
Retrofit is a type-safe HTTP client for Android and Java. Retrofit makes it easy to connect to a REST web service by translating the API into Java interfaces. In this tutorial, I'll show you how to use one of the most popular and often recommended HTTP libraries available for Android.
This powerful library makes it easy to consume JSON or XML data, which is then parsed into Plain Old Java Objects (POJOs). GET
, POST
, PUT
, PATCH
, and DELETE
requests can all be executed.
Like most open-source software, Retrofit was built on top of some other powerful libraries and tools. Behind the scenes, Retrofit makes use of OkHttp (from the same developer) to handle network requests. Also, Retrofit does not have a built-in JSON converter to parse from JSON to Java objects. Instead, it ships support for the following JSON converter libraries to handle that:
- Gson:
com.squareup.retrofit:converter-gson
- Jackson:
com.squareup.retrofit:converter-jackson
- Moshi:
com.squareup.retrofit:converter-moshi
For Protocol buffers, Retrofit supports:
- Protobuf:
com.squareup.retrofit2:converter-protobuf
- Wire:
com.squareup.retrofit2:converter-wire
And for XML Retrofit, supports:
- Simple Framework:
com.squareup.retrofit2:converter-simpleframework
So Why Use Retrofit?
Developing your own type-safe HTTP library to interface with a REST API can be a real pain: you have to handle many aspects, such as making connections, caching, retrying failed requests, threading, response parsing, error handling, and more. Retrofit, on the other hand, is a well-planned, documented and tested library that will save you a lot of precious time and headaches.
In this tutorial, I will explain how to use Retrofit 2 to handle network requests by building a simple app that will perform POST
requests, PUT
requests (to update entities), and DELETE
requests. I'll also show you how to integrate with RxJava and how to cancel requests. We'll be using the API provided by JSONPlaceholder—this is a fake online REST API for testing and prototyping.
Check out my previous post, Get Started With Retrofit 2 HTTP Client, to learn how to execute GET
requests and how to integrate Retrofit with RxJava.
1. Create an Android Studio Project
Fire up Android Studio and create a new project with an empty activity called MainActivity
.
2. Declaring Dependencies
After creating a new project, declare the following dependencies in your build.gradle
. The dependencies include the Retrofit library and also Google's Gson library to convert JSON to POJO (Plain Old Java Objects) as well as Retrofit's Gson integration.
// Retrofit compile 'com.squareup.retrofit2:retrofit:2.1.0' // JSON Parsing compile 'com.google.code.gson:gson:2.6.1' compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Make sure you sync your project after adding the dependencies.
3. Adding Internet Permission
To perform network operations, we need to include the INTERNET
permission in the application manifest: AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.chikeandroid.retrofittutorial2"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".PostActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
4. Generating Models Automatically
We are going to create models automatically from the JSON response data by leveraging a very useful tool: jsonschema2pojo. We would like to make a POST
request (create a new resource) on the API. But before we execute this request, we need to know the JSON response we should expect when it is executed successfully so that Retrofit can parse the JSON response and deserialize it to Java objects. According to the API, if we send the following data in a POST
request:
data: { title: 'foo', body: 'bar', userId: 1 }
We should get the following response:
{ "title": "foo", "body": "bar", "userId": 1, "id": 101 }
Map the JSON Data to Java
Copy the sample response data from the previous section. Now visit jsonschema2pojo and paste the JSON response into the input box. Select source type of JSON, annotation style of Gson, uncheck Allow additional properties, and change the class name from Example to Post.
Then click the Preview button to generate the Java objects.
You might be wondering what the @SerializedName
and @Expose
annotations do in this generated code! Don't worry, I'll explain it all!
The @SerializedName
annotation is needed for Gson to map the JSON keys to Java object fields.
@SerializedName("userId") @Expose private Integer userId;
In this case, the JSON key userId
is mapped to the class field userId
. But note that since they are the same, there is no need to include the @SerializedName
annotation on the field because Gson will map it automatically for us.
The @Expose
annotation indicates that the class member should be exposed for JSON serialization or deserialization.
Import Data Models to Android Studio
Now let's go back to Android Studio. Create a new sub-package inside the main
package and name it data
. Inside the newly created package, create another package and name it model
. Inside this package, create a new Java class and name it Post
. Now copy the Post
class that was generated by jsonschema2pojo and paste it inside the Post
class you created.
package com.chikeandroid.retrofittutorial2.data.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; public class Post { @SerializedName("title") @Expose private String title; @SerializedName("body") @Expose private String body; @SerializedName("userId") @Expose private Integer userId; @SerializedName("id") @Expose private Integer id; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public String toString() { return "Post{" + "title='" + title + '\'' + ", body='" + body + '\'' + ", userId=" + userId + ", id=" + id + '}'; } }
Apart from the getters and setters, I also included the toString()
method. (In Intellij, you can use the Generate command to make this easy: Alt-Insert on Windows, or Command-N on macOS.)
5. Creating the Retrofit Instance
To issue network requests to a RESTful API with Retrofit, we need to create an instance using the Retrofit Builder class and configure it with a base URL.
Create a new sub-package inside the data
package and name it remote
. Now, inside this package, create a Java class and name it RetrofitClient
. This class will create a singleton of Retrofit in the method getClient(String baseUrl)
and return it to the caller.
As I mentioned earlier, Retrofit needs a base URL to build its instance, so we will pass it a URL when calling RetrofitClient.getClient(String baseUrl)
. This URL will then be used to build the instance in line 12. We are also specifying the JSON converter we need (Gson) in line 13.
package com.chikeandroid.retrofittutorial2.data.remote; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { private static Retrofit retrofit = null; public static Retrofit getClient(String baseUrl) { if (retrofit==null) { retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }
6. Creating the API Interface
Inside the remote package, create an interface and call it APIService
. This interface contains methods we are going to use to execute HTTP requests such as POST
, PUT
, and DELETE
. Let's start with the POST
request.
package com.chikeandroid.retrofittutorial2.data.remote; import com.chikeandroid.retrofittutorial2.data.model.Post; import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; public interface APIService { @POST("/posts") @FormUrlEncoded Call<Post> savePost(@Field("title") String title, @Field("body") String body, @Field("userId") long userId); }
Looking at the APIService
class, we have a method called savePost()
. On top of the method is the @POST
annotation, which indicates that we want to execute a POST
request when this method is called. The argument value for the @POST
annotation is the endpoint—which is /posts
. So the full URL will be http://jsonplaceholder.typicode.com/posts.
Okay, so what about the @FormUrlEncoded
? This will indicate that the request will have its MIME type (a header field that identifies the format of the body of an HTTP request or response) set to application/x-www-form-urlencoded
and also that its field names and values will be UTF-8 encoded before being URI-encoded. The @Field("key")
annotation with parameter name should match the name that the API expects. Retrofit implicitly converts the values to strings using String.valueOf(Object)
, and the strings are then form URL encoded. null
values are ignored.
For example, calling APIService.savePost("My Visit To Lagos", "I visited...", 2)
yields a request body of title=My+Visit+To+Lagos&body=I+visited...&userId=2
.
Using the @Body
Annotation
We can also use the @Body
annotation on a service method parameter instead of specifying a form-style request body with a number of individual fields. The object will be serialized using the Retrofit
instance Converter
specified during creation. This is only used when performing either a POST
or PUT
operation.
@POST("/posts") @FormUrlEncoded Call<Post> savePost(@Body Post post);
7. Creating the API Utilities
We are going to create a utility class. So create a class in data.remote
and name it ApiUtils
. This class will have the base URL as a static variable and will also provide the APIService
interface by with a getAPIService()
static method to the rest of our application.
package com.chikeandroid.retrofittutorial2.data.remote; public class ApiUtils { private ApiUtils() {} public static final String BASE_URL = "http://jsonplaceholder.typicode.com/"; public static APIService getAPIService() { return RetrofitClient.getClient(BASE_URL).create(APIService.class); } }
Make sure you end the base URL with a /
.
8. Creating the Layout
The file activity_main.xml is the layout for our MainActivity
. This layout will have one text edit field for the title of the post and another for the body of the post. It also includes a button to submit the post to the API.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_post" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.chikeandroid.retrofittutorial2.AddEditPostActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:textAppearance="@style/TextAppearance.AppCompat.Title" android:text="@string/title_enter_post"/> <EditText android:id="@+id/et_title" android:layout_marginTop="18dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_title"/> <EditText android:id="@+id/et_body" android:lines="4" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_body"/> <Button android:id="@+id/btn_submit" android:layout_marginTop="18dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent" android:textColor="@android:color/white" android:text="@string/action_submit"/> <TextView android:id="@+id/tv_response" android:layout_marginTop="35dp" android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
9. Executing the POST request
In the onCreate()
method in MainActivity
, we initialize an instance of the APIService
interface (line 14). We also initialize the EditText
fields and a submit button that will call the sendPost()
method when clicked (line 22).
private TextView mResponseTv; private APIService mAPIService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final EditText titleEt = (EditText) findViewById(R.id.et_title); final EditText bodyEt = (EditText) findViewById(R.id.et_body); Button submitBtn = (Button) findViewById(R.id.btn_submit); mResponseTv = (TextView) findViewById(R.id.tv_response); mAPIService = ApiUtils.getAPIService(); submitBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String title = titleEt.getText().toString().trim(); String body = bodyEt.getText().toString().trim(); if(!TextUtils.isEmpty(title) && !TextUtils.isEmpty(body)) { sendPost(title, body); } } }); }
In the sendPost(String, String)
method in the MainActivity
class, we passed in the title and body of the post to this method. What this method will do is call our API service interface method savePost(String, String)
whose job is to execute a POST
request sending the title and body to the API. The showResponse(String response)
method will display the response on the screen.
public void sendPost(String title, String body) { mAPIService.savePost(title, body, 1).enqueue(new Callback<Post>() { @Override public void onResponse(Call<Post> call, Response<Post> response) { if(response.isSuccessful()) { showResponse(response.body().toString()); Log.i(TAG, "post submitted to API." + response.body().toString()); } } @Override public void onFailure(Call<Post> call, Throwable t) { Log.e(TAG, "Unable to submit post to API."); } }); } public void showResponse(String response) { if(mResponseTv.getVisibility() == View.GONE) { mResponseTv.setVisibility(View.VISIBLE); } mResponseTv.setText(response); }
Our APIService
instance mAPIService
method savePost(String, String)
will return a Call
instance which has a method called enqueue(Callback<T> callback)
.
Understanding enqueue()
enqueue()
asynchronously sends the request and notifies your app with a callback when a response comes back. Since this request is asynchronous, Retrofit handles the execution on a background thread so that the main UI thread isn't blocked or interfered with.
To use the enqueue()
method, you have to implement two callback methods: onResponse()
and onFailure()
. Only one of these methods will be called in response to a given request.
-
onResponse()
: invoked for a received HTTP response. This method is called for a response that can be correctly handled even if the server returns an error message. So if you get a status code of 404 or 500, this method will still be called. To get the status code in order for you to handle situations based on them, you can use the methodresponse.code()
. You can also use theisSuccessful()
method to find out if the status code is in the range 200-300, indicating success. -
onFailure()
: invoked when a network exception occurred communicating to the server or when an unexpected exception occurred handling the request or processing the response.
Synchronous Requests
To perform a synchronous request, you can use the execute()
method in a Call
instance. But be aware that synchronous methods on the main/UI thread will block any user action. So don't execute synchronous methods on Android's main/UI thread! Instead run them on a background thread.
Using RxJava
RxJava was integrated into Retrofit 1 by default, but in Retrofit 2 you need to include some extra dependencies. Retrofit ships with a default adapter for executing Call
instances. So you can change Retrofit's execution mechanism to include RxJava by including the RxJava CallAdapter
. These are the steps:
Step 1
Add the dependencies.
compile 'io.reactivex:rxjava:1.1.6' compile 'io.reactivex:rxandroid:1.2.1' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
Step 2
Add the new CallAdapter RxJavaCallAdapterFactory.create()
when building a Retrofit instance (line 5).
public static Retrofit getClient(String baseUrl) { if (retrofit==null) { retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; }
Step 3
Update the APIService
savePost(String title, String body, String userId)
method to become an Observable.
@POST("/posts") @FormUrlEncoded Observable<Post> savePost(@Field("title") String title, @Field("body") String body, @Field("userId") long userId);
Step 4
When making the requests, our anonymous subscriber responds to the observable's stream which emits events, in our case Post
. The onNext
method is then called when our subscriber receives any event, which is then passed to our showResponse(String response)
method.
public void sendPost(String title, String body) { // RxJava mAPIService.savePost(title, body, 1).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Post>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Post post) { showResponse(post.toString()); } }); }
Check out Getting Started With ReactiveX on Android by Ashraff Hathibelagal to learn more about RxJava and RxAndroid.
10. Testing the App
At this point, you can run the app and click the submit button when you have entered a title and body. The response from the API will show below the submit button.
11. Executing a PUT request
Now that we know how to execute a POST
request, let's see how we can execute a PUT
request which updates entities. Add the following new method to the APIService
class.
@PUT("/posts/{id}") @FormUrlEncoded Call<Post> updatePost(@Path("id") long id, @Field("title") String title, @Field("body") String body, @Field("userId") long userId);
To update a post from the API, we have the endpoint /posts/{id}
with {id}
being a placeholder for the id of the post we want to update. The @Path
annotation is the named replacement in a URL path segment {id}
. Be aware that values are converted to string using String.valueOf(Object)
and URL encoded. If the value is already encoded, you can disable URL encoding like this: @Path(value="name", encoded=true)
.
12. Executing a DELETE request
Let's also see how to execute a DELETE
request. Using the JSONPlaceholder API, to delete a post resource, the required endpoint is /posts/{id}
with the HTTP method DELETE
. Back to our APIService
interface, we just need to include the method deletePost()
that will execute it. We pass in the id of the post to the method, and it is replaced in the URL path segment {id}
.
@DELETE("/posts/{id}") Call<Post> deletePost(@Path("id") long id);
13. Canceling a Request
Let's say you want to give your users the ability to cancel or abort a request. This is very easy to do in Retrofit. The Retrofit Call
class has a method called cancel()
that will do just that (line 32 below). This method will trigger the onFailure()
method in the callback.
This method can be called, for example, if there is no internet connection or when an unexpected exception occurred creating the request or handling the response. So to know if the request was canceled, use the method isCanceled()
in the Call
class (line 20).
private Call<Post> mCall; ... public sendPost(String title, String body) { mCall = mAPIService.savePost(title, body, 1); mCall.enqueue(new Callback<Post>() { @Override public void onResponse(Call<Post> call, Response<Post> response) { if(response.isSuccessful()) { showResponse(response.body().toString()); Log.i(TAG, "post submitted to API." + response.body().toString()); } } @Override public void onFailure(Call<Post> call, Throwable t) { if(call.isCanceled()) { Log.e(TAG, "request was aborted"); }else { Log.e(TAG, "Unable to submit post to API."); } showErrorMessage(); } }); } public void cancelRequest() { mCall.cancel(); }
Conclusion
In this tutorial, you learned about Retrofit: why you should use it and how to integrate it in your project to perform POST
, PUT
, DELETE
and cancel requests. You also learned how to integrate RxJava with it. In my next post in using Retrofit, I'll show you how to upload files.
To learn more about Retrofit, do refer to the official documentation. And check out some of our other tutorials and courses about Android development here at Envato Tuts+!
Comments