Sending Data With Retrofit 2 HTTP Client for Android

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.

Sending Data With Retrofit 2 HTTP Client for Android

Sending Data With Retrofit 2 HTTP Client for Android

This powerful library makes it easy to consume JSON or XML data, which is then parsed into Plain Old Java Objects (POJOs). GETPOSTPUTPATCH, 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.

  • Sending Data With Retrofit 2 HTTP Client for Android
    Android SDK
    Get Started With Retrofit 2 HTTP Client
    Chike Mgbemena

1. Create an Android Studio Project

Fire up Android Studio and create a new project with an empty activity called MainActivity.

Sending Data With Retrofit 2 HTTP Client for Android

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.

Sending Data With Retrofit 2 HTTP Client for Android

Then click the Preview button to generate the Java objects.

Sending Data With Retrofit 2 HTTP Client for Android

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 13. We are also specifying the JSON converter we need (Gson) in line 14.

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 POSTPUT, 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 method response.code(). You can also use the isSuccessful() 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.

  • Sending Data With Retrofit 2 HTTP Client for Android
    Android
    Getting Started With ReactiveX on Android
    Ashraff Hathibelagal

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.

Sending Data With Retrofit 2 HTTP Client for Android

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 30 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 18).

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 POSTPUTDELETE 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 СodeHolder Tuts+!

  • Sending Data With Retrofit 2 HTTP Client for Android

    Animate Your Android App

    Animations have become an important part of the Android user experience. Subtle, well-crafted animation is used throughout the Android OS and can make your…
  • Sending Data With Retrofit 2 HTTP Client for Android

    Practical Concurrency on Android With HaMeR

    In this tutorial we’ll explore the HaMeR (Handler, Message and Runnable) framework, one of the most powerful concurrency models available on Android. With a…
  • Sending Data With Retrofit 2 HTTP Client for Android

    Coding Functional Android Apps in Kotlin: Getting Started

    Heard positive things about the Kotlin language for Android apps and want to try it for yourself? Find out how to set up and start coding in this new…
  • Sending Data With Retrofit 2 HTTP Client for Android

    Migrate an Android App to Material Design

    Years ago, when Android was still a budding mobile operating system, it was rather notorious for its ugly user interface. Because there were no design…