How to get remote resource with Retrofit in Android Android 22.06.2016

android_retrofit_icon.png

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. 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.

In older architecture of Android networking, if you want to get response from server you should use three libs

  • AsyncTask to do all background task.
  • HttpClient or HttpurlConnection to do networking task.
  • JsonParser class to parse data comes from server.

With Volley or Retrofit you have nothing to do everything is automatic.

Volley VS Retrofit

Android Volley (developed by Google) and Retrofit (developed by Square, Inc) are two of the most used libraries for accessing REST web APIs today. These libraries not only ease the development effort but also, give you many great features like retry mechanism, automatic parsing of data, caching and loading image.

Retrofit is a simple REST client for Android without caching and image loading. What makes it different is that, Retrofit can perform synchronous or asynchronous HTTP with automatic JSON parsing without any effort. Retrofit does the JSON parsing automatically using GSON.

Android Volley can capture four types of responses automatically through these requests: StringRequest, JsonObjectRequest, JsonArrayRequest, ImageRequest. On the other hand Retrofit can parse many other types of responses automatically like: Boolean, Integer, Date, String, Object, Collections.

One of the great things about Volley is that it supports retries on request timeout. Retrofit on the other hand does not have a retry mechanism as of now.

Android Volley library has a very elaborate caching mechanism. Retrofit on the hand, does not support caching.

Volley library has a special type of request to get images from network called ImageRequest. As of now Retrofit does not support the loading of images. You can use Glide or Picasso.

Use Retrofit if your use-case is a standard REST API with JSON responses and not too many custom requirements in terms of caching, request prioritization, retries, etc. Use Volley if you have unusual / fine-grained requirements, or if you anticipate needing a lot of flexibility from your networking layer in the future at the cost of more code. source

Retrofit is 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.

Retrofit does not have a built-in any 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

Retrofit supports XML via:

  • Simple Framework: com.squareup.retrofit2:converter-simpleframework

GET request

For this tutorial I will use JSONPlaceholder as data source.

Our plan is

  1. Create a new project using your Android Studio.
  2. Add Retrofit2, GSON, RecylerView and CardView dependencies to build.gradle (Module:app).
  3. Add INTERNET permissions in AndroidManifest.xml file.
  4. Create model class.
  5. Create Retrofit instance.
  6. Define endpoints.
  7. Add RecylerView to our activity_main.xml. Set Recylerview in MainActivity.
  8. Make request.

1. Open your Android Studio and create a new project.

2. Go to GradleScripts > build.gradle (Module:app) and add below libraries to this file under dependencies block. My dependencies block is

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.0.1'

    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.android.support:cardview-v7:25.0.1'

    compile 'com.google.code.gson:gson:2.8.2'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
}

3. Since we are working with network operations we need to add INTERNET permissions in AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="me.proft.retrofitdemo">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application>
    ...
    </application>
</manifest>

4. First, we need to know what type of JSON response we will be receiving and then create Java objects that will be able to parse posts. Based on the JSON response returned for this API call, let’s first define how a basic post representation should look like.

The Retrofit GSON converter takes care of converting the JSON objects and store it in our model class. Create a class named PostItem.

import com.google.gson.annotations.SerializedName;

public class PostItem {
    @SerializedName("id")
    private Integer id;
    @SerializedName("userId")
    private Integer userId;
    @SerializedName("title")
    private String title;
    @SerializedName("body")
    private String body;

    public PostItem(Integer id, Integer userId, String title, String body) {
        this.id = id;
        this.userId = userId;
        this.title = title;
        this.body = body;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

The @SerializedName annotation is needed for Gson to map the JSON keys with our fields. In keeping with Java's camelCase naming convention for class member properties, it is not recommended to use underscores to separate words in a variable. @SerializedName helps translate between the two.

5. To send network requests to an API, we need to use the Retrofit Builder class and specify the base URL for the service. So, create a class named ApiClient.

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class ApiClient {
    public static final String BASE_URL = "http://jsonplaceholder.typicode.com/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

6. The endpoints are defined inside of an interface using special Retrofit annotations to encode details about the parameters and request method. This interface contains methods we are going to use to execute HTTP requests such as GET, POST, PUT, PATCH, and DELETE.

In addition, the return value is always a parameterized Call<T> object such as Call<PostItem>. For instance, ApiInterface interface defines each endpoint in the following way.

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;

public interface ApiInterface {
    @GET("posts/")
    Call<List<PostItem>> getPosts();

    @GET("posts/{id}")
    Call<PostItem> getPostDetails(@Path("id") int id);

    @GET("posts/")
    Call<List<PostItem>> getPostsByCategory(
        @Query("category") String category, 
        @Query("order") String order
    )
}

Each endpoint specifies an annotation of the HTTP method (GET, POST, etc.) and the parameters of this method can also have special annotations (@Query, @Path, @Body etc.)

Take a look to other annotations

  • @Path - variable substitution for the API endpoint. For example PostItem id will be swapped for {id} in the URL endpoint.
  • @Query – specifies the query key name with the value of the annotated parameter.
  • @Header – specifies the header with the value of the annotated parameter.
  • @Body - sends Java objects as request body.
  • @Field - send data as form-urlencoded. This requires a @FormUrlEncoded annotation attached with the method. The @Field parameter works only with a POST.

Sometimes you want to filter the data from the server. Retrofit has the @Query("key") annotation to use instead of hard-coding it in the endpoint. The key value represents the parameter name in the URL. It will be added to the URL by Retrofit. For example, if we pass the value "linux" as an argument to the getPostsByCategory(String category, null) method, the full URL will be:

https://blog.com/api/posts?category=linux

7. Let's visualize response. Open your activity_main.xml file and add Recyclerview to it

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rcView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="16dp"
        android:paddingTop="16dp"
        android:scrollbars="vertical"
         />
</RelativeLayout>

Create a new layout for single row of RecyclerView using CardView. Below is content of file post_item.xml.

<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    android:background="#C5CAE9"
    android:foreground="?attr/selectableItemBackground"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    card_view:cardCornerRadius="2dp">

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:minHeight="72dp"
        android:orientation="horizontal"
        android:id="@+id/llPosts"
        android:padding="16dp">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tvUserId"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingRight="16dp"
                android:text="UserID"
                android:textColor="@color/colorGrey"
                />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="top"
                android:textStyle="bold"
                android:textSize="16sp"
                android:textColor="@color/colorGrey" />

            <TextView
                android:id="@+id/tvBody"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="3"
                android:textColor="@color/colorGreyLight" />
        </LinearLayout>
    </LinearLayout>
</android.support.v7.widget.CardView>

Create a RecylerViewHolder java class for RecyclerView by extending RecyclerView.ViewHolder. Also create an RecylerAdapter java class for RecyclerView by extending RecyclerView.Adapter. More About RecyclerView in Android.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.PostViewHolder> {
    private List<PostItem> posts;
    private int rowLayout;
    private Context context;
    private ItemListener itemListener;

    public class PostViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        LinearLayout llPosts;
        TextView tvUserId;
        TextView tvTitle;
        TextView tvBody;
        ItemListener itemListener;

        public PostViewHolder(View v, ItemListener itemListener) {
            super(v);
            llPosts = (LinearLayout) v.findViewById(R.id.llPosts);
            tvUserId = (TextView) v.findViewById(R.id.tvUserId);
            tvTitle = (TextView) v.findViewById(R.id.tvTitle);
            tvBody = (TextView) v.findViewById(R.id.tvBody);

            this.itemListener = itemListener;
            v.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            PostItem item = getItem(getAdapterPosition());
            this.itemListener.onPostClick(item.getId());

            notifyDataSetChanged();
        }        
    }

    public PostAdapter(List<PostItem> posts, int rowLayout, Context context, ItemListener itemListener) {
        this.posts = posts;
        this.context = context;
        this.rowLayout = rowLayout;
        this.itemListener = itemListener;
    }

    @Override
    public PostAdapter.PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(rowLayout, parent, false);
        return new PostViewHolder(view);
    }

    @Override
    public void onBindViewHolder(PostViewHolder holder, final int position) {
        holder.tvTitle.setText(posts.get(position).getTitle());
        holder.tvBody.setText(posts.get(position).getBody());
        holder.tvUserId.setText(posts.get(position).getUserId().toString());
    }

    @Override
    public int getItemCount() {
        return posts.size();
    }

    public void updateItems(List items) {
        this.posts = items;
        notifyDataSetChanged();
    }

    private PostItem getItem(int pos) {
        return posts.get(pos);
    }

    public interface ItemListener {
        void onPostClick(long id);
    }    
}

8. Let’s make the first request from our MainActivity. If we want to consume the API asynchronously, we call the service as follows. Open the MainActivity.java and do the below changes.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rcView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);

        Call<List<PostItem>> call = apiService.getPosts();

        call.enqueue(new Callback<List<PostItem>>() {
            @Override
            public void onResponse(Call<List<PostItem>>call, Response<List<PostItem>> response) {
                List<PostItem> posts = response.body();
                recyclerView.setAdapter(new PostAdapter(posts, R.layout.post_item, getApplicationContext(), 
                    new PostAdapter.ItemListener() {
                        @Override
                        public void onPostClick(long id) {
                            Toast.makeText(MainActivity.this, "Post ID is " + id, Toast.LENGTH_SHORT).show();
                        }
                    }));
            }

            @Override
            public void onFailure(Call<List<PostItem>>call, Throwable t) {
                Log.e(TAG, t.toString());
            }
        });
    }
}

Calls may be executed synchronously with execute(), or asynchronously with 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 it on a background thread so that the main UI thread isn't blocked or interfered with.

To use enqueue(), you have to implement two callback methods:

  • 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.

You can also call the API synchronously.

try {            
     List<PostItem> items = apiService.getPosts().execute().body();
} catch (IOException e) {
     e.printStackTrace();
}

You cannot run this code in main tread you will get exception like java.lang.RuntimeException: ... android.os.NetworkOnMainThreadException. So better to use synchronous request in Services or AsyncTask.

So, Retrofit will download and parse the API data on a background thread, and then return the results back to the UI thread via the onResponse or onFailure method.

Result

android_retrofit_demo.png

Code on github

POST request

Let's see how we can execute a POST request which gets entities. Add the following new method to the ApiInterface class.

public interface ApiInterface {
    @POST("posts/")
    @FormUrlEncoded
    Call<PostItem> savePost(@Field("title") String title,
                        @Field("body") String body,
                        @Field("userId") long userId);
}

On top of the savePost() 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.

@FormUrlEncoded 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.

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/")
Call<PostItem> saveObject(@Body Post post);

In the savePost(String, String, long) method in the MainActivity class, we passed in the title, body and id of the post to this method. It job is to execute a POST request sending the title, body and id to the API.

api.savePost(title, body, 1).enqueue(new Callback<PostItem>() {
    @Override
    public void onResponse(Call<PostItem> call, Response<PostItem> response) {
        if(response.isSuccessful()) {
            Log.d(TAG, "Post submitted to API." + response.body().toString());
        }
    }

    @Override
    public void onFailure(Call<PostItem> call, Throwable t) {
        Log.d(TAG, "Unable to submit post to API.");
    }
});

Also you can send Multipart forms.

PUT request

Let's see how we can execute a PUT request which updates entities. Add the following new method to the ApiInterface class.

@PUT("posts/{id}/")
@FormUrlEncoded
Call<PostItem> 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).

DELETE request

Let's also see how to execute a DELETE request. The required endpoint is posts/{id}/ with the HTTP method DELETE. Back to our ApiInterface 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<PostItem> deletePost(@Path("id") long id);

How to log request to server

Interceptors are a powerful mechanism present in OkHttp that can monitor, rewrite, and retry calls.

Interceptors can be majorly divided into two categories:

  • Application Interceptors. To register an application interceptor, we need to call addInterceptor() on OkHttpClient.Builder.
  • Network Interceptors. To register a network Interceptor, invoke addNetworkInterceptor() instead of addInterceptor().

Add the library dependency to the dependencies section of the build.gradle (Module: app) file:

compile 'com.squareup.okhttp3:logging-interceptor:3.4.0'

Add client() method to the Retrofit.Builder() with logging interceptor.

// here a logging interceptor is created
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

// the logging interceptor will be added to the http client
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging);

// the Retrofit builder will have the client attached, in order to get connection logs
Retrofit retrofit = new Retrofit.Builder()
    .client(httpClient.build())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl(BASE_URL)
    .build();

How to download file from server

Add the following code before opening the <application> in the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

The interface for Retrofit REST service, in this case, the GET, the @Streaming enables the downloading for large file.

public interface APIDownloadFile {
    @Streaming
    @GET
    Call<ResponseBody> downloadFileByUrl(@Url String fileUrl);
}

If you're downloading a large file, Retrofit would try to move the entire file into memory. In order to avoid that, we've to add @Streaming annotation to the request declaration.

The sample code for downloading the file.

public void downloadFile() {
    Retrofit.Builder builder = new Retrofit.Builder().baseUrl("http://server.com/");
    Retrofit retrofit = builder.build();
    APIDownloadFile downloadService = retrofit.create(APIDownloadFile.class);

    Call<ResponseBody> call = downloadService.downloadFileByUrl("http://10.0.2.2/media/photo.jpg");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                Log.d(TAG, "Got the body for the file");

                new AsyncTask<Void, Long, Void>() {
                    @Override
                    protected Void doInBackground(Void... voids) {
                        saveToDisk(response.body());
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Void result) {
                        super.onPostExecute(result);
                        File destinationFile = new File("/data/data/" + getPackageName() + "/photos/photo.jpg");
                        iv.setImageURI(android.net.Uri.parse(destinationFile.toURI().toString()));
                    }                    
                }.execute();

            } else {
                Log.d(TAG, "Connection failed " + response.errorBody());
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            t.printStackTrace();
            Log.d(TAG, t.getMessage());
        }
    });
}

The sample code for saving the file to android internal/external storage.

public void saveToDisk(ResponseBody body) {
    try {
        // save to internal storage
        new File("/data/data/" + getPackageName() + "/photos").mkdir();
        File destinationFile = new File("/data/data/" + getPackageName() + "/photos/photo.jpg");

        // save external storage
        // check permission
        //File destinationFile = new File(Environment.getExternalStoragePublicDirectory(
        //        Environment.DIRECTORY_PICTURES), "photo.jpg");

        InputStream is = null;
        OutputStream os = null;

        try {
            Log.d(TAG, "File size = " + body.contentLength());

            is = body.byteStream();
            os = new FileOutputStream(destinationFile);

            byte data[] = new byte[4096];
            int count;
            int progress = 0;
            while ((count = is.read(data)) != -1) {
                os.write(data, 0, count);
                progress +=count;
                Log.d(TAG, "Progress: " + progress + "/" + body.contentLength();
            }

            os.flush();

            Log.d(TAG, "File saved successfully!");
            return;
        } catch (IOException e) {
            e.printStackTrace();
            Log.d(TAG, "Failed to save the file!");
            return;
        } finally {
            if (is != null) is.close();
            if (os != null) os.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
        Log.d(TAG, "Failed to save the file!");
        return;
    }
}

Retrofit Rxjava Android Example

RxJava is a java implementation of Reactive Streams standard. It is a specification of API for processing asynchronous processing of streams.

Retrofit 2 works seamlessly with RxJava 2 using the RxJava 2 adapter for Retrofit 2, add the following dependency to enable the RxJava 2 compatibility

compile 'com.google.code.gson:gson:2.8.2'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

Also don’t forgot to add dependency for internet permission in AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

Retrofit services can return any type of object, but to make Retrofit support the type of object you want in your project, you need to provide adapters. Retrofit rxjava adapter makes retrofit return Observables.

Once the dependencies are all set up, we would need an instance of Retrofit to make network calls

public class ApiClient {
    public static final String BASE_URL = "http://jsonplaceholder.typicode.com/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        }
        return retrofit;
    }
}
  • baseUrl() - sets the API base url.
  • addConverterFactory() -  converter factory for serialization and deserialization of objects.
  • addCallAdapterFactory() - adapter factory for supporting service method return types, add instance of RxJava2CallAdapterFactory for Rxjava 2 support.

The apis are declared in an interface

public interface ApiInterface {
    @GET("posts/")
    Observable<List<PostItem>> getPosts();

    @GET("posts/{id}/comments")
    Observable<List<CommentItem>> getComments(@Path("id") int id);
}

Making an api call

// Retrofit instance which was created earlier
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);

// Return type as defined in TwitterApi interface
Observable<List<PostItem>> items = apiService.getPosts();

Thats it! You can use the Observable returned by the API and subscribe() to it to handle the data

items.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<List<PostItem>>() {
        @Override
        public void onSubscribe(@NonNull Disposable d) {}

        @Override
        public void onNext(@NonNull List<PostItem> postItems) {
            Log.d(TAG, "onNext: " + postItems);
        }

        @Override
        public void onError(@NonNull Throwable e) {}

        @Override
        public void onComplete() {}
    });

Also we can add ProgressDialog and Retrolambda.

ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Observable<List<PostItem>> items = apiService.getPosts();

ProgressDialog progress;
progress = new ProgressDialog(this);
progress.setMessage("Loading...");

items.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnSubscribe(disposable -> progress.dismiss())
    .doOnComplete(() -> progress.dismiss())
    .subscribe(
        posts -> {
            Log.d(TAG, "Items: " + posts);
        },
        err -> {
            err.printStackTrace();
            progress.dismiss();
        });

Retrofit Rxjava Multiple Calls and Examples. In the case of single retrofit service call, you can directly subscribe to the observable returned by the retrofit service and process results. But if you need to make multiple services call to full fill data needs of a user request, you will have to use RxJava operators to transform, combine, and merger results.

Let’s go over scenarios where multiple retrofit service calls are made to combine Post item and all comments for it.

Following is PostItem class

public class PostItem {
    @SerializedName("id")
    private Integer id;
    @SerializedName("userId")
    private Integer userId;
    @SerializedName("title")
    private String title;
    @SerializedName("body")
    private String body;

    private List<CommentItem> comments;

    public PostItem(Integer id, Integer userId, String title, String body) {
        this.id = id;
        this.userId = userId;
        this.title = title;
        this.body = body;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<CommentItem> getComments() {
        return comments;
    }

    public void setComments(List<CommentItem> comments) {
        this.comments = comments;
    }

    @Override
    public String toString() {
        return "PostItem{" +
                "id=" + id +
                ", userId=" + userId +
                ", comments=" + (comments == null ? 0 : comments.size()) +
                '}';
    }
}

Following is CommentItem class

public class CommentItem {
    @SerializedName("postId")
    private Integer postId;
    @SerializedName("id")
    private Integer id;
    @SerializedName("name")
    private String name;
    @SerializedName("email")
    private String email;
    @SerializedName("body")
    private String body;

    public Integer getPostId() {
        return postId;
    }

    public void setPostId(Integer postId) {
        this.postId = postId;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    @Override
    public String toString() {
        return "CommentItem{" +
                "postId=" + postId +
                ", id=" + id +
                '}';
    }
}

Add following snippet inside onCreate of your Activity.

ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Observable<List<PostItem>> posts = apiService.getPosts();

ProgressDialog progress;
progress = new ProgressDialog(this);
progress.setMessage("Loading...");

posts
    .flatMap(Observable::fromIterable)
    .flatMap(post -> {
        Observable<List<CommentItem>> comments = apiService.getComments(post.getId());
        return Observable.zip(comments,
                Observable.just(post),
                Pair::new);
    })
    .map(pairs -> {
        pairs.second.setComments(pairs.first);
        return pairs.second;
    })
    .take(10)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnSubscribe(disposable -> progress.show())
    .doOnComplete(() -> progress.dismiss())
    .subscribe(
        items -> {
            Log.d(TAG, "Posts: " + items);
        },
        err -> {
            err.printStackTrace();
            progress.dismiss();
        });

Useful links