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?
We’ll be using Java 8 in our Android Application to unleash lambda expressions.
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:
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.
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'
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.
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.
subscribeOn() vs observeOn()
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.
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);
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.
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: 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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
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.
How to write a Unitest Case for this ?
- Pragnesh Prajapati
Sir this app is running but It is showing the handle error method toast when opening the app.
- Vipul
[ { “UserId”: “0000106955”, “UserType”: “U”, “UserName”: “Dharm”, “Password”: “wzNfU2Chtfo=” } how to use onNext() Array and object } ]
- sugandha
thanks for sharing ur knowledge
- benk
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
Hi, thanks for sharing! How can i get the response http status code in this way?
- Daniel Hernández