Tutorial

Android RxJava and Retrofit

Published on August 3, 2022
author

Anupam Chugh

Android RxJava and Retrofit

In this tutorial, we’ll be implementing Retrofit calls using RxJava in android app. We’ll be creating an application that populates a RecyclerView using Retrofit and RxJava. We’ll be using a CryptoCurrency API.

What Will You Learn?

  • Making a Retrofit call using RxJava.
  • Doing Multiple Retrofit Calls Using RxJava
  • Transforming the Retrofit POJO response using RxJava

We’ll be using Java 8 in our Android Application to unleash lambda expressions.

Overview

Retrofit is a REST client that uses OkHttp as the HttpClient and JSON Parsers to parse the response. We’ll use gson as the JSON Parser here. This is how a Retrofit instance is created:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Gson gson = new GsonBuilder()
                .setLenient()
                .create();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

HttpLoggingInterceptor is used to log the data during the network call. RxJava is a library that is used for asynchronous and reactive programming in the form of streams. We use different threads in RxJava. A background thread for the network call and the main thread for updating the UI. Schedulers in RxJava is responsible for performing operations using different threads. RxAndroid is an extension of RxJava and it contains the Android threads to be used in the Android Environment. To use RxJava in retrofit environment we need to do just two major changes:

  • Add the RxJava in Retrofit Builder.
  • Use Observable type in the interface instead of Call

To do multiple calls or transform the response, we use RxJava operators. Let’s see how it’s done through our sample application below.

Project Structure

android-rxjava-retrofit-project-structure Add the following dependencies in our build.gradle file:

    implementation 'com.android.support:cardview-v7:27.1.0'
    implementation 'com.android.support:design:27.1.0'
    implementation('com.squareup.retrofit2:retrofit:2.3.0')
            {
                exclude module: 'okhttp'
            }

    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'

Code

The code for the layout activity_main.xml is given below.

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

The code for the CryptocurrencyService.java class is given below.

package com.journaldev.rxjavaretrofit;

import com.journaldev.rxjavaretrofit.pojo.Crypto;

import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;

public interface CryptocurrencyService {


    String BASE_URL = "https://api.cryptonator.com/api/full/";

    @GET("{coin}-usd")
    Observable<Crypto> getCoinData(@Path("coin") String coin);
}

@Path passes in the path we specify into the curly braces. Note: The @Path parameter name must match with the one in the @GET. The POJO class Crypto.java is given below:

package com.journaldev.rxjavaretrofit.pojo;

import com.google.gson.annotations.SerializedName;
import java.util.List;

public class Crypto {

    @SerializedName("ticker")
    public Ticker ticker;
    @SerializedName("timestamp")
    public Integer timestamp;
    @SerializedName("success")
    public Boolean success;
    @SerializedName("error")
    public String error;


    public class Market {

        @SerializedName("market")
        public String market;
        @SerializedName("price")
        public String price;
        @SerializedName("volume")
        public Float volume;

        public String coinName;

    }

    public class Ticker {

        @SerializedName("base")
        public String base;
        @SerializedName("target")
        public String target;
        @SerializedName("price")
        public String price;
        @SerializedName("volume")
        public String volume;
        @SerializedName("change")
        public String change;
        @SerializedName("markets")
        public List<Market> markets = null;

    }
}

coinName is a field we’ve set. Using the magic of RxJava we’ll set a value on this field to transform the response.

Creating a Single Call Using RxJava

CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);
        
Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");
cryptoObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(result -> result.ticker)
.subscribe(this::handleResults, this::handleError);

subscribeOn() creates a Scheduler thread over which we do the network call. We can pass any of the following Schedulers in it.

  • trampoline(): This runs the tasks on the current thread. So it’ll run your code after the current task on the thread is complete. Useful for queueing operations.
  • newThread(): Creates and returns a Scheduler that creates a new Thread for each unit of work. This is costly since it creates a separate thread everytime.
  • computation(): Creates and returns a Scheduler intended for computational work. This should be used for parallel work since the thread pool is bound. I/O operations shouldn’t be done here.
  • io(): Creates and returns a Scheduler intended for IO-bound work. Again it’s bounded like computation. Typically this is used for network calls.

subscribeOn() vs observeOn()

  • subscribeOn works downstream and upstream. All the tasks above and below it would use the same thread.
  • observeOn works downstream only.
  • consecutive subscribeOn methods won’t change the thread. Only the first subscribeOn thread would be used.
  • consecutive observeOn methods will change the thread.
  • After an observeOn(), putting a subscribeOn() won’t change the thread. Hence observeOn should generally come after a subscribeOn.

AndroidSchedulers.mainThread() is a part of RxAndroid and is used to observe the data on the main thread only. subscribe method is what triggers the retrofit call and gets the data in the method handleResults which we’ll see shortly.

Multiple Calls

We use the RxJava operator merge to do two retrofit calls one after the other.

Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc");

Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth");

Observable.merge(btcObservable, ethObservable)
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::handleResults, this::handleError);

Transforming the Response

To transform the POJO response we can do the following:

Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")
                .map(result -> Observable.fromIterable(result.ticker.markets))
                .flatMap(x -> x).filter(y -> {
                    y.coinName = "btc";
                    return true;
                }).toList().toObservable();

        Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")
                .map(result -> Observable.fromIterable(result.ticker.markets))
                .flatMap(x -> x).filter(y -> {
                    y.coinName = "eth";
                    return true;
                }).toList().toObservable();

        Observable.merge(btcObservable, ethObservable)
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::handleResults, this::handleError);

We use Observable.fromIterable to convert the the map result into Observable streams. flatMap works on the elements one by one. Thus converting the ArrayList to single singular elements. In the filter method we change the response. toList() is used to convert the results of flatMap back into a List. toObservable() wraps them as Observable streams.

MainActivity.java

The code for the MainActivity.java class is given below:

package com.journaldev.rxjavaretrofit;

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 android.widget.Toast;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.journaldev.rxjavaretrofit.pojo.Crypto;

import java.util.List;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

import static com.journaldev.rxjavaretrofit.CryptocurrencyService.BASE_URL;

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    Retrofit retrofit;
    RecyclerViewAdapter recyclerViewAdapter;

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

        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerViewAdapter = new RecyclerViewAdapter();
        recyclerView.setAdapter(recyclerViewAdapter);


        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Gson gson = new GsonBuilder()
                .setLenient()
                .create();

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();


        callEndpoints();
    }

    private void callEndpoints() {

        CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);

        //Single call
        /*Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");
        cryptoObservable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).map(result -> result.ticker).subscribe(this::handleResults, this::handleError);*/

        Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")
                .map(result -> Observable.fromIterable(result.ticker.markets))
                .flatMap(x -> x).filter(y -> {
                    y.coinName = "btc";
                    return true;
                }).toList().toObservable();

        Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")
                .map(result -> Observable.fromIterable(result.ticker.markets))
                .flatMap(x -> x).filter(y -> {
                    y.coinName = "eth";
                    return true;
                }).toList().toObservable();

        Observable.merge(btcObservable, ethObservable)
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::handleResults, this::handleError);



    }


    private void handleResults(List<Crypto.Market> marketList) {
        if (marketList != null && marketList.size() != 0) {
            recyclerViewAdapter.setData(marketList);


        } else {
            Toast.makeText(this, "NO RESULTS FOUND",
                    Toast.LENGTH_LONG).show();
        }
    }

    private void handleError(Throwable t) {

        Toast.makeText(this, "ERROR IN FETCHING API RESPONSE. Try again",
                Toast.LENGTH_LONG).show();
    }

}

handleResults and handleError are invoked using the Java 8 invocation :: In the handlResults we set the converted response on the ReyclerViewAdapter. handleError() is invoked if the response has an error. The code for the recyclerview_item_layout layout is given below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="16dp">

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp">

            <TextView
                android:id="@+id/txtCoin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginTop="8dp"
                android:textAllCaps="true"
                android:textColor="@android:color/black"
                app:layout_constraintHorizontal_bias="0.023"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/txtMarket"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintHorizontal_bias="0.025"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/txtCoin" />

            <TextView
                android:id="@+id/txtPrice"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintHorizontal_bias="0.025"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/txtMarket" />


        </android.support.constraint.ConstraintLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

The code for the RecyclerViewAdapter.java class is given below:

package com.journaldev.rxjavaretrofit;

import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.journaldev.rxjavaretrofit.pojo.Crypto;

import java.util.ArrayList;
import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    private List<Crypto.Market> marketList;


    public RecyclerViewAdapter() {
        marketList = new ArrayList<>();
    }

    @Override
    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                             int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_layout, parent, false);

        RecyclerViewAdapter.ViewHolder viewHolder = new RecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) {
        Crypto.Market market = marketList.get(position);
        holder.txtCoin.setText(market.coinName);
        holder.txtMarket.setText(market.market);
        holder.txtPrice.setText("$" + String.format("%.2f", Double.parseDouble(market.price)));
        if (market.coinName.equalsIgnoreCase("eth")) {
            holder.cardView.setCardBackgroundColor(Color.GRAY);
        } else {
            holder.cardView.setCardBackgroundColor(Color.GREEN);
        }
    }

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

    public void setData(List<Crypto.Market> data) {
        this.marketList.addAll(data);
        notifyDataSetChanged();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView txtCoin;
        public TextView txtMarket;
        public TextView txtPrice;
        public CardView cardView;

        public ViewHolder(View view) {
            super(view);

            txtCoin = view.findViewById(R.id.txtCoin);
            txtMarket = view.findViewById(R.id.txtMarket);
            txtPrice = view.findViewById(R.id.txtPrice);
            cardView = view.findViewById(R.id.cardView);
        }
    }
}

The output of the above application in action is given below: android-rxjava-retrofit-output The above output merges the results of the Bitcoin and Ethereum market prices done through retrofit. This brings an end to this tutorial. You can download the Android RxJavaRetrofit Project from the link below.

RxJavaRetrofit

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Anupam Chugh

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
October 8, 2021

How to write a Unitest Case for this ?

- Pragnesh Prajapati

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    November 2, 2020

    Sir this app is running but It is showing the handle error method toast when opening the app.

    - Vipul

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      May 5, 2020

      [ { “UserId”: “0000106955”, “UserType”: “U”, “UserName”: “Dharm”, “Password”: “wzNfU2Chtfo=” } how to use onNext() Array and object } ]

      - sugandha

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        November 23, 2019

        thanks for sharing ur knowledge

        - benk

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          December 3, 2018

          hi, Im using Mvvm pattern to select images from gallery using picasso, but Im unable to set the selected image to imageview, where image exist.

          - pravee n

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            June 18, 2018

            Hi, thanks for sharing! How can i get the response http status code in this way?

            - Daniel Hernández

              Try DigitalOcean for free

              Click below to sign up and get $200 of credit to try our products over 60 days!

              Sign up

              Join the Tech Talk
              Success! Thank you! Please check your email for further details.

              Please complete your information!

              Become a contributor for community

              Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

              DigitalOcean Documentation

              Full documentation for every DigitalOcean product.

              Resources for startups and SMBs

              The Wave has everything you need to know about building a business, from raising funding to marketing your product.

              Get our newsletter

              Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

              New accounts only. By submitting your email you agree to our Privacy Policy

              The developer cloud

              Scale up as you grow — whether you're running one virtual machine or ten thousand.

              Get started for free

              Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

              *This promotional offer applies to new accounts only.