Retrofit Calls With RxJava In Android

Introduction

 
There are several ways to make a REST API call like AsyncTask, Volley, etc. Nowadays, with the increasing popularity of RxJava, developers are preferring to use this library to make asynchronous API calls efficiently.
 
According to the docs "A type-safe HTTP client for Android and Java". This is simply a library that is used to make an API call that uses JSON parser to parse the response.
 
Step 1
 
Add gradle dependencies to your build.gradle.
  1. dependencies {    
  2.     implementation fileTree(dir: 'libs', include: ['*.jar'])    
  3.     implementation 'com.android.support:appcompat-v7:27.1.0'    
  4.     implementation 'com.android.support.constraint:constraint-layout:1.0.2'    
  5.     implementation 'com.android.support:cardview-v7:27.1.0'    
  6.     implementation 'com.android.support:design:27.1.0'    
  7.     testImplementation 'junit:junit:4.12'    
  8.     androidTestImplementation 'com.android.support.test:runner:1.0.1'    
  9.     androidTestImplementation    
  10.     
  11.     implementation 'com.android.support:cardview-v7:27.1.0'    
  12.     implementation 'com.android.support:design:27.1.0'    
  13.     implementation('com.squareup.retrofit2:retrofit:2.3.0')    
  14.             {    
  15.                 exclude module: 'okhttp'    
  16.             }    
  17.     
  18.     implementation 'com.squareup.retrofit2:converter-gson:2.3.0'    
  19.     implementation 'io.reactivex.rxjava2:rxjava:2.1.9'    
  20.     implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'    
  21.     implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'    
  22.     implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'    
  23. }    
Here, we can see the dependencies, cardview, and design are for recyclerview lists. We need a converter for parsing the response into a valid JSON.
 
Step 2
 
Create an instance of Retrofit and interceptor as well. Here, Interceptor is used for logging the data during a network call. We generally use different threads in RxJava - a background thread for the network call and the main thread for updating the UI. Schedulers in RxJava are responsible for performing operations using different threads. 
  1. HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();    
  2.        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);    
  3.        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();    
  4.     
  5.        Gson gson = new GsonBuilder()    
  6.                .setLenient()    
  7.                .create();    
  8.     
  9.        retrofit = new Retrofit.Builder()    
  10.                .baseUrl(BASE_URL)    
  11.                .client(client)    
  12.                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())    
  13.                .addConverterFactory(GsonConverterFactory.create(gson))    
  14.                .build();   
Since we are using Retrofit in RxJava environment, we need to make some changes as below.
  • Adding RxJava in Retrofit Builder.
  • Use Observable type in the interface instead of Call. Call is generally used with Retrofit.
Step 3
 
Let's see the activity_main.xml 
  1. <?xml version="1.0" encoding="utf-8"?>    
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  3.     xmlns:tools="http://schemas.android.com/tools"    
  4.     android:layout_width="match_parent"    
  5.     android:layout_height="match_parent"    
  6.     tools:context=".MainActivity">    
  7.     
  8.     <android.support.v7.widget.RecyclerView    
  9.         android:id="@+id/recyclerView"    
  10.         android:layout_width="match_parent"    
  11.         android:layout_height="match_parent" />    
  12.     
  13. </android.support.constraint.ConstraintLayout>   
Since we are creating a list, so we have to take a recyclerview and must code an adapter to hold the data in the list.
 
Now create an interface before making an API call. Let's have a look at the full interface code below.
  1. String BASE_URL = "https://api.cryptonator.com/api/full/";  
  2.   
  3.    @GET("{coin}-usd")  
  4.    Observable<Crypto> getCoinData(@Path("coin") String coin);  
Since "coin" is an input param, this is the syntax of Retrofit we have to provide in @Path whenever we use this method.
 
Step 4
 
Make a POJO class or in other words, a data model class named as Crypto.java.
  1. public class Crypto {  
  2.   
  3.     @SerializedName("ticker")  
  4.     public Ticker ticker;  
  5.     @SerializedName("timestamp")  
  6.     public Integer timestamp;  
  7.     @SerializedName("success")  
  8.     public Boolean success;  
  9.     @SerializedName("error")  
  10.     public String error;  
  11.   
  12.   
  13.     public class Market {  
  14.   
  15.         @SerializedName("market")  
  16.         public String market;  
  17.         @SerializedName("price")  
  18.         public String price;  
  19.         @SerializedName("volume")  
  20.         public Float volume;  
  21.   
  22.         public String coinName;  
  23.   
  24.     }  
  25.   
  26.     public class Ticker {  
  27.   
  28.         @SerializedName("base")  
  29.         public String base;  
  30.         @SerializedName("target")  
  31.         public String target;  
  32.         @SerializedName("price")  
  33.         public String price;  
  34.         @SerializedName("volume")  
  35.         public String volume;  
  36.         @SerializedName("change")  
  37.         public String change;  
  38.         @SerializedName("markets")  
  39.         public List<Market> markets = null;  
  40.   
  41.     }  
  42. }  
coinName is a field we’ve set. Using the magic of RxJava, we’ll set a value on this field to transform the response. 
 
Step 5 - Single Call
 
Make a single call using RxJava or we can make two calls and later merge them. We will see this using merge operator of Rxjava.
  1. //Single call  
  2.        Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");  
  3.        cryptoObservable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())  
  4.        .map(result -> Observable.fromIterable(result.ticker.markets))  
  5.                .flatMap(x -> x).filter(y -> {  
  6.            y.coinName = "btc";  
  7.            return true;  
  8.        }).toList().toObservable()  
  9.        .subscribe(this::handleResults, this::handleError);  
Step 6 - Multiple Calls
 
Now, make two Retrofit calls and merge them using RxJava operator merge.
  1. CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);  
  2.   
  3.        Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")  
  4.                .map(result -> Observable.fromIterable(result.ticker.markets))  
  5.                .flatMap(x -> x).filter(y -> {  
  6.                    y.coinName = "btc";  
  7.                    return true;  
  8.                }).toList().toObservable();  
  9.   
  10.        Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")  
  11.                .map(result -> Observable.fromIterable(result.ticker.markets))  
  12.                .flatMap(x -> x).filter(y -> {  
  13.                    y.coinName = "eth";  
  14.                    return true;  
  15.                }).toList().toObservable();  
  16.   
  17.        Observable.merge(btcObservable, ethObservable)  
  18.                .subscribeOn(Schedulers.computation())  
  19.                .observeOn(AndroidSchedulers.mainThread())  
  20.                .subscribe(this::handleResults, this::handleError);  
Here, we are merging two calls - one is btcObservable and another is ethObservable.
 
Analysis
  • We use Observable.fromIterable to convert 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.
See the whole code in activity called MainActivity.java. Since we are taking multiple call cases, so there is no need to show the single call.
  1. import android.support.v7.app.AppCompatActivity;    
  2. import android.os.Bundle;    
  3. import android.support.v7.widget.LinearLayoutManager;    
  4. import android.support.v7.widget.RecyclerView;    
  5. import android.widget.Toast;    
  6. import com.google.gson.Gson;    
  7. import com.google.gson.GsonBuilder;    
  8. import com.journaldev.rxjavaretrofit.pojo.Crypto;    
  9. import java.util.List;    
  10. import io.reactivex.Observable;    
  11. import io.reactivex.android.schedulers.AndroidSchedulers;    
  12. import io.reactivex.schedulers.Schedulers;    
  13. import okhttp3.OkHttpClient;    
  14. import okhttp3.logging.HttpLoggingInterceptor;    
  15. import retrofit2.Retrofit;    
  16. import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;    
  17. import retrofit2.converter.gson.GsonConverterFactory;    
  18.     
  19.     
  20. public class MainActivity extends AppCompatActivity {    
  21.     
  22.     RecyclerView recyclerView;    
  23.     Retrofit retrofit;    
  24.     RecyclerViewAdapter recyclerViewAdapter;    
  25.     
  26.     @Override    
  27.     protected void onCreate(Bundle savedInstanceState) {    
  28.         super.onCreate(savedInstanceState);    
  29.         setContentView(R.layout.activity_main);    
  30.     
  31.         recyclerView = findViewById(R.id.recyclerView);    
  32.         recyclerView.setLayoutManager(new LinearLayoutManager(this));    
  33.         recyclerViewAdapter = new RecyclerViewAdapter();    
  34.         recyclerView.setAdapter(recyclerViewAdapter);    
  35.     
  36.     
  37.         HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();    
  38.         interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);    
  39.         OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();    
  40.     
  41.         Gson gson = new GsonBuilder()    
  42.                 .setLenient()    
  43.                 .create();    
  44.     
  45.         retrofit = new Retrofit.Builder()    
  46.                 .baseUrl(CryptocurrencyService.BASE_URL)    
  47.                 .client(client)    
  48.                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())    
  49.                 .addConverterFactory(GsonConverterFactory.create(gson))    
  50.                 .build();    
  51.     
  52.     
  53.         callEndpoints();    
  54.     }    
  55.     
  56.     private void callEndpoints() {    
  57.     
  58.         CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);    
  59.     
  60.         Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")    
  61.                 .map(result -> Observable.fromIterable(result.ticker.markets))    
  62.                 .flatMap(x -> x).filter(y -> {    
  63.                     y.coinName = "btc";    
  64.                     return true;    
  65.                 }).toList().toObservable();    
  66.     
  67.         Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")    
  68.                 .map(result -> Observable.fromIterable(result.ticker.markets))    
  69.                 .flatMap(x -> x).filter(y -> {    
  70.                     y.coinName = "eth";    
  71.                     return true;    
  72.                 }).toList().toObservable();    
  73.     
  74.         Observable.merge(btcObservable, ethObservable)    
  75.                 .subscribeOn(Schedulers.computation())    
  76.                 .observeOn(AndroidSchedulers.mainThread())    
  77.                 .subscribe(this::handleResults, this::handleError);      
  78.     }      
  79.     private void handleResults(List<Crypto.Market> marketList) {    
  80.         if (marketList != null && marketList.size() != 0) {    
  81.             recyclerViewAdapter.setData(marketList);      
  82.         } else {    
  83.             Toast.makeText(this"NO RESULTS FOUND",    
  84.                     Toast.LENGTH_LONG).show();    
  85.         }    
  86.     }    
  87.     private void handleError(Throwable t) {    
  88.         Toast.makeText(this"ERROR IN FETCHING API RESPONSE. Try again",    
  89.                 Toast.LENGTH_LONG).show();    
  90.     }      
  91. }    
Here, we are using handleResults and handleError is invoked using the Java 8 invocation. Converted response must be set in the ReyclerViewAdapter.
 
The Adapter items xml is below. See recyclerview_item_layout.xml,
  1. <?xml version="1.0" encoding="utf-8"?>    
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"    
  4.     android:layout_width="match_parent"    
  5.     android:layout_height="wrap_content">    
  6.     
  7.     <android.support.v7.widget.CardView    
  8.         android:id="@+id/cardView"    
  9.         android:layout_width="match_parent"    
  10.         android:layout_height="wrap_content"    
  11.         android:layout_gravity="center"    
  12.         android:layout_margin="16dp">    
  13.     
  14.         <android.support.constraint.ConstraintLayout    
  15.             android:layout_width="match_parent"    
  16.             android:layout_height="wrap_content"    
  17.             android:padding="8dp">    
  18.     
  19.             <TextView    
  20.                 android:id="@+id/txtCoin"    
  21.                 android:layout_width="wrap_content"    
  22.                 android:layout_height="wrap_content"    
  23.                 android:layout_marginLeft="8dp"    
  24.                 android:layout_marginRight="8dp"    
  25.                 android:layout_marginTop="8dp"    
  26.                 android:textAllCaps="true"    
  27.                 android:textColor="@android:color/black"    
  28.                 app:layout_constraintHorizontal_bias="0.023"    
  29.                 app:layout_constraintLeft_toLeftOf="parent"    
  30.                 app:layout_constraintRight_toRightOf="parent"    
  31.                 app:layout_constraintTop_toTopOf="parent" />    
  32.     
  33.             <TextView    
  34.                 android:id="@+id/txtMarket"    
  35.                 android:layout_width="wrap_content"    
  36.                 android:layout_height="wrap_content"    
  37.                 android:layout_marginLeft="8dp"    
  38.                 android:layout_marginRight="8dp"    
  39.                 android:layout_marginTop="8dp"    
  40.                 app:layout_constraintHorizontal_bias="0.025"    
  41.                 app:layout_constraintLeft_toLeftOf="parent"    
  42.                 app:layout_constraintRight_toRightOf="parent"    
  43.                 app:layout_constraintTop_toBottomOf="@+id/txtCoin" />    
  44.     
  45.             <TextView    
  46.                 android:id="@+id/txtPrice"    
  47.                 android:layout_width="wrap_content"    
  48.                 android:layout_height="wrap_content"    
  49.                 android:layout_marginLeft="8dp"    
  50.                 android:layout_marginStart="8dp"    
  51.                 android:layout_marginTop="8dp"    
  52.                 app:layout_constraintHorizontal_bias="0.025"    
  53.                 app:layout_constraintLeft_toLeftOf="parent"    
  54.                 app:layout_constraintRight_toRightOf="parent"    
  55.                 app:layout_constraintTop_toBottomOf="@+id/txtMarket" />    
  56.     
  57.     
  58.         </android.support.constraint.ConstraintLayout>    
  59.     </android.support.v7.widget.CardView>    
  60. </LinearLayout>    
Finally, we have an adapter class named as RecyclerViewAdapter.java.
  1. public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {      
  2.     private List<Crypto.Market> marketList;      
  3.     public RecyclerViewAdapter() {    
  4.         marketList = new ArrayList<>();    
  5.     }      
  6.     @Override    
  7.     public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,    
  8.                                                              int viewType) {    
  9.     
  10.         View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_layout, parent, false);    
  11.     
  12.         RecyclerViewAdapter.ViewHolder viewHolder = new RecyclerViewAdapter.ViewHolder(view);    
  13.         return viewHolder;    
  14.     }      
  15.     @Override    
  16.     public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) {    
  17.         Crypto.Market market = marketList.get(position);    
  18.         holder.txtCoin.setText(market.coinName);    
  19.         holder.txtMarket.setText(market.market);    
  20.         holder.txtPrice.setText("$" + String.format("%.2f", Double.parseDouble(market.price)));    
  21.         if (market.coinName.equalsIgnoreCase("eth")) {    
  22.             holder.cardView.setCardBackgroundColor(Color.GRAY);    
  23.         } else {    
  24.             holder.cardView.setCardBackgroundColor(Color.GREEN);    
  25.         }    
  26.     }    
  27.     
  28.     @Override    
  29.     public int getItemCount() {    
  30.         return marketList.size();    
  31.     }    
  32.     
  33.     public void setData(List<Crypto.Market> data) {    
  34.         this.marketList.addAll(data);    
  35.         notifyDataSetChanged();    
  36.     }    
  37.     
  38.     public class ViewHolder extends RecyclerView.ViewHolder {      
  39.         public TextView txtCoin;    
  40.         public TextView txtMarket;    
  41.         public TextView txtPrice;    
  42.         public CardView cardView;    
  43.     
  44.         public ViewHolder(View view) {    
  45.             super(view);    
  46.     
  47.             txtCoin = view.findViewById(R.id.txtCoin);    
  48.             txtMarket = view.findViewById(R.id.txtMarket);    
  49.             txtPrice = view.findViewById(R.id.txtPrice);    
  50.             cardView = view.findViewById(R.id.cardView);    
  51.         }    
  52.     }    
  53. }    
Output
 
 
You can see the green color lines and grey color lines - one for "btc" and another for "eth" respectively. This is a mix data of two API calls. Above, we have created two Observables - one for btc and another for eth.
  

Conclusion

 
In this article, we learned how to set up retrofit and instance making. The article mainly focused on the Retrofit bond with RxJava. Here, we used merge operator of RxJava to combine two Retrofit calls.