Retrofit and RxJava android example

In this tutorial, well be implementing Retrofit calls using RxJava in android app. Well be creating an application that populates a RecyclerView using Retrofit and RxJava. Well 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

Well 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.
Well 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.
Lets see how its done through our sample application below.

Project Structure

Retrofit and RxJava android example
Retrofit and RxJava android example

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.

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 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 markets = null; } }

coinName is a field weve set. Using the magic of RxJava well set a value on this field to transform the response.

Creating a Single Call Using RxJava

CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class); Observable 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 itll 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 shouldnt be done here.
  • io(): Creates and returns a Scheduler intended for IO-bound work. Again its 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 wont change the thread. Only the first subscribeOn thread would be used.
  • consecutive observeOn methods will change the thread.
  • After an observeOn(), putting a subscribeOn() wont 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 well see shortly.

Multiple Calls

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

Observable> btcObservable = cryptocurrencyService.getCoinData("btc"); Observable> 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> btcObservable = cryptocurrencyService.getCoinData("btc") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "btc"; return true; }).toList().toObservable(); Observable> 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 cryptoObservable = cryptocurrencyService.getCoinData("btc"); cryptoObservable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).map(result -> result.ticker).subscribe(this::handleResults, this::handleError);*/ Observable> btcObservable = cryptocurrencyService.getCoinData("btc") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "btc"; return true; }).toList().toObservable(); Observable> 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 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.

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 { private List 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 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:

Retrofit and RxJava android example
Retrofit and RxJava android example

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