ZOFTINO.COM android and web dev tutorials

Retrofit Rxjava Android Example

Using retrofit and rxjava, you can easily develop components to get data from reset services and update view objects with results from background process.

Retrofit is an http client using which you can build easy to maintain and enhance rest clients. With retrofit, you don’t need to use low level http objects in your code, all you need to do is to define interface for your service call, configure retrofit builder and set required converters and adapters.

You can learn more about retrofit by reading retrofit concepts and how to use retrofit to build rest clients in android tutorial.

RxJava and RxAndroid allow you to build backend components in android, which can execute multiple reset service calls parallel, chain service calls, process results, and update view object in main thread by utilizing RxJava features such schedulers, observeOn, subscribeOn and other operators.

You can learn more about Rxjava by reading RxJava examples and Rxjava operators posts.

Setup

You need to add below dependencies to use retrofit and rxjava in android.

		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'

Retrofit Rxjava

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.

Notice that one of the library dependency mentioned above is retrofit rxjava adapter.

You can set adapter by calling addCallAdapterFactory method on Retrofit.Builder using RxJava2CallAdapterFactory class.

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

After this setup, retrofit service call can return rxjava observable objects. So, next step in using retrofit with rxjava is to define service interface which returns observable.

 public interface StoreCouponsApi {
    @GET("coupons/")
    Observable<StoreCoupons> getCoupons(@Query("status") String status);
    @GET("storeOffers/")
    Observable<StoreCoupons> getStoreInfo();
}
 

Finally make a call to service that runs in the background and updates UI with results on android main thread. SubscribeOn makes it run in the background thread and observeOn is what makes it possible to return the execution to main thread to execute subscriber code and update view objects with results from service call. RxAndroid provides AndroidSchedulers class that can get hold of android main thread, on which subscriber gets results.

 retrofit.create(StoreCouponsApi.class).getCoupons("topcoupons")
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::handleResults, this::handleError );
 

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 few scenarios where multiple retrofit service calls are made parallel or results from service calls are transformed, modified, or merged using rxjava operators.

Making two service calls parallel and combining results using retrofit and rxjava.

 Observable.just(retrofit.create(StoreCouponsApi.class)).subscribeOn(Schedulers.computation())
        .flatMap(s -> {
            Observable<StoreCoupons> couponsObservable
                    = s.getCoupons("topcoupons").subscribeOn(Schedulers.io());

            Observable<StoreCoupons> storeInfoObservable
                    = s.getStoreInfo().subscribeOn(Schedulers.io());

            return Observable.merge(couponsObservable,storeInfoObservable);
        }).observeOn(AndroidSchedulers.mainThread()).subscribe(this::handleResults, this::handleError );
 

Making second service call based on results from first service call using retrofit and rxjava. Function supplied to flatMap makes first retrofit service call, map is applied on resulting observable from the service call to get response and then again flatMap is used to make a second service call using data from previous service call and consumer is subscribed to final observable.

 Observable.just(retrofit.create(StoreCouponsApi.class))
          .subscribeOn(Schedulers.computation())
          .flatMap(s -> {                    
                 return s.getStoreInfo().subscribeOn(Schedulers.io())
                         .map(res -> res.getStore())
                         .flatMap( store -> s.getCoupons(store));
           }).observeOn(AndroidSchedulers.mainThread()).subscribe(this::handleResults, this::handleError );
 

You can transform or modify response from retrofit service call using rxjava operators. In below example, map operator is used to modify value of response.

 retrofit.create(StoreCouponsApi.class).getStoreInfo()
        .subscribeOn(Schedulers.io())
        .map(item -> {item.setMaxCashback("Max Cashback "+item.getMaxCashback()); return item;})
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::handleResults, this::handleError );
 

If you want to filter some records from the response received from retrofit service call, you can use filter operator. In the below example, first it makes a call to retrofit service, then flatMap is used on the observable returned by retrofit service to create an observable that emits required data from response and finally you apply filter on each item so that only items which satisfy the condition will be finally emitted.

 retrofit.create(StoreCouponsApi.class).getCoupons("topcoupons")
        .subscribeOn(Schedulers.io())
        .flatMap(storeCpn -> {return Observable.fromIterable(storeCpn.getCoupons());})
        .filter(coupon ->  !(coupon.getExpiryDate().equals( "todayDt")))
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::handleResults, this::handleError );
 

If there is an error in getting response from service, you can retry service call for certain number of times and with certain time gap between two calls using retrofit and rxjava.

 retrofit.create(StoreCouponsApi.class).getCoupons("topcoupons")
        .subscribeOn(Schedulers.io())
        .retry(4)
        .timer(200, java.util.concurrent.TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::handleResults, this::handleError );
 

Retrofit Rxjava Android Example

Below is an example which shows how to use Retrofit and Rxjava in android. On clicking a button, a retrofit service call is made in the background and view objects are updated with results.

This example also shows how to use and style card view with recycler view.

This project is available on git at https://github.com/srinurp/RetrofitRxJavaAndroid

android retrofit rxjava use cases & examples

Add dependencies and permissions

In addition to above mentioned retrofit and rxjava dependencies, you need to add below dependencies for this example. And also add internet permission to manifest xml file.

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.android.support:design:25.3.1' 

Retrofit Interface

 package com.zoftino.rxjavaretrofit;


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

public interface StoreCouponsApi {
    @GET("coupons/")
    Observable<StoreCoupons> getCoupons(@Query("status") String status);
    @GET("storeOffers/")
    Observable<StoreCoupons> getStoreInfo();
}
 

Activity

 package com.zoftino.rxjavaretrofit;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends AppCompatActivity {

    public static final String BASE_URL = "http://www.zoftino.com/api/";

    //keeps track of subscriptions
    private CompositeDisposable compositeDisposable;

    private RecyclerView couponRecyclerView;

    private Retrofit retrofit;

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

        //set layout manager for recyclerView
        couponRecyclerView = (RecyclerView) findViewById(R.id.coupon_rv);
        RecyclerView.LayoutManager couponLayoutManager = new LinearLayoutManager(this);
        couponRecyclerView.setLayoutManager(couponLayoutManager);

        //configure Retrofit using Retrofit Builder
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public void showCoupons(View view){
        getcouponData();
    }
    public void showCouponsTopStore(View view){
        getStoreCouponData();
    }
    //two Retrofit service calls execute parallel using RxJava
    private void getStoreCouponData(){
        //first it creates an observable which emits retrofit service class
        //to leave current main thread, we need to use subscribeOn which subscribes the observable on computation thread
        //flatMap is used to apply function on the item emitted by previous observable
        //function makes two rest service calls using the give retrofit object for defined api interface
        //these two calls run parallel that is why subscribeOn is used on each of them
        //since these two api call return same object, they are joined using concatArray operator
        //finally consumer observes on android main thread
        Observable.just(retrofit.create(StoreCouponsApi.class)).subscribeOn(Schedulers.computation())
                .flatMap(s -> {
            Observable<StoreCoupons> couponsObservable
                    = s.getCoupons("topcoupons").subscribeOn(Schedulers.io());

            Observable<StoreCoupons> storeInfoObservable
                    = s.getStoreInfo().subscribeOn(Schedulers.io());

             return Observable.concatArray(couponsObservable,storeInfoObservable);
        }).observeOn(AndroidSchedulers.mainThread()).subscribe(this::handleResults, this::handleError );

    }
    //single api call using retrofit and rxjava
    private void getcouponData(){
        retrofit.create(StoreCouponsApi.class).getCoupons("topcoupons")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::handleResults, this::handleError );
    }

    private void handleResults(StoreCoupons storeCoupons){
        if(storeCoupons.getCoupons() != null){
            CouponsAdapter ca = new CouponsAdapter(storeCoupons.getCoupons(), MainActivity.this);
            couponRecyclerView.setAdapter(ca);
        }else{
            TextView store_name = (TextView) findViewById(R.id.store_name);
            store_name.setText(storeCoupons.getStore());
            TextView coupon_count = (TextView) findViewById(R.id.coupon_count);
            coupon_count.setText(storeCoupons.getTotalCoupons());
            TextView max_cashback = (TextView) findViewById(R.id.max_cashback);
            max_cashback.setText(storeCoupons.getMaxCashback());
        }
    }

    private void handleError(Throwable t){
       Log.e("Observer", ""+ t.toString());
        Toast.makeText(this, "ERROR IN GETTING COUPONS",
                Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onDestroy() {
        //dispose subscriptions
        if (compositeDisposable != null && !compositeDisposable.isDisposed()) {
            compositeDisposable.clear();
        }
        super.onDestroy();
    }
}
 

RecyclerView Adapter

 package com.zoftino.rxjavaretrofit;

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

import java.util.List;

class CouponsAdapter extends RecyclerView.Adapter<CouponsAdapter.ViewHolder> {

    private List<Coupon> couponList;
    private Context context;

    public CouponsAdapter(List<Coupon> cpnList, Context ctx) {
        couponList = cpnList;
        context = ctx;
    }

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

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

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

    @Override
    public void onBindViewHolder(CouponsAdapter.ViewHolder holder, int position) {
        Coupon coupon = couponList.get(position);
        holder.store.setText(coupon.getStore());
        holder.coupon.setText(coupon.getCoupon());
        holder.expiry.setText(coupon.getExpiryDate());
        holder.code.setText(coupon.getCouponCode());
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        public TextView store;
        public TextView coupon;
        public TextView expiry;
        public TextView code;

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

            store = (TextView) view.findViewById(R.id.store);
            coupon = (TextView) view.findViewById(R.id.coupon);
            expiry = (TextView) view.findViewById(R.id.expiry);
            code = (TextView) view.findViewById(R.id.coupon_code);
            view.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            Toast.makeText(context, "You chose coupon " + getAdapterPosition(),
                    Toast.LENGTH_LONG).show();
        }
    }
}
 

RecyclerView Item Layout

 <!--?xml version="1.0" encoding="utf-8"?-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coupon_row"
android:layout_width="match_parent"
android:layout_height="wrap_content">

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

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

            <TextView
                android:id="@+id/store"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/zoftinoText"
                android:layout_marginRight="8dp"
                app:layout_constraintRight_toRightOf="parent"
                android:layout_marginLeft="8dp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                android:layout_marginTop="16dp"
                app:layout_constraintHorizontal_bias="0.023"></TextView>

            <TextView
                android:id="@+id/coupon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/zoftinoText"
                android:layout_marginLeft="8dp"
                app:layout_constraintLeft_toLeftOf="parent"
                android:layout_marginRight="8dp"
                app:layout_constraintRight_toRightOf="parent"
                android:layout_marginTop="10dp"
                app:layout_constraintTop_toBottomOf="@+id/store"
                app:layout_constraintHorizontal_bias="0.025"></TextView>

            <TextView
                android:id="@+id/expiry"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/zoftinoText"
                android:layout_marginLeft="8dp"
                app:layout_constraintLeft_toLeftOf="parent"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toBottomOf="@+id/coupon"></TextView>

            <TextView
                android:id="@+id/coupon_code"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/zoftinoText"
                app:layout_constraintLeft_toRightOf="@+id/expiry"
                android:layout_marginLeft="8dp"
                app:layout_constraintBaseline_toBaselineOf="@+id/expiry"
                android:layout_marginRight="8dp"
                app:layout_constraintRight_toRightOf="parent"></TextView>

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