ZOFTINO.COM android and web dev tutorials

Android Persistency Room RxJava

It is very important to provide offline functionality in the applications for features that depend on remote services for data needs so that when users are out of network, they can continue to use applications. This is possible by storing data on the device. So, when internet is off, application will use local data, making the related functionally available to users.

To store data on the device, we can use android persistency framework Room. Room helps you build components to access SQLite database.

Room has many advantages compared to SQLite API. You can read my previous articles android persistent library Room and database migration with room to know how to use room.

One of the features of room that helps you build reactive applications is that room DAOs can return observables. Room with observable makes it possible to display modified data in UI as it changes in the database. One of the android architectural components is LiveData, an observable, that can be used with Room to build reactive applications. My previous post explains how room can be used with livedata.

In this post, I’ll show how to use Room with RxJava. RxJava can be a better option compared to LiveData when transformation of resulting observable from Room is required, as RxJava provides several operators to perform transformations.

Setup

Add google maven repository to your project build.gradle file.

maven { url 'https://maven.google.com' }

Add below dependencies to module build.gradle file

For Lifecyle and ViewModel

compile "android.arch.lifecycle:runtime:1.0.0-alpha5"
compile "android.arch.lifecycle:extensions:1.0.0-alpha5"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha5"

For Room

compile "android.arch.persistence.room:runtime:1.0.0-alpha5"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"

For RxJava-Room

compile "android.arch.persistence.room:rxjava2:1.0.0-alpha5"

Room RxJava Retrofit Dagger Example

I’ll show how to use Room with Rxjava by taking coupon application. This example also shows how to use dagger, and Retrofit with RxJava.

The application has one screen, on opening the screen, it will fetch latest coupons data from remote service using Retrofit and RxJava and it will store the remote data on the device using Room.

When activity starts, it fetches coupon data from SQLite database using Room as Flowable observable and populates the data in RecyclerView to display on screen. With this, if data is modified in the database, the change will be reflected in UI via RxJava Flowable returned by Room.

Complete example is available on git at https://github.com/srinurp/RoomRxJava

Below are the components

Room Entity

Create a POJO class for each table that you want to create in database and annotate the class with Entity. Fields in the entity class represent columns in the table. In the below example, Id filed is marked as PrimaryKey and autoGenerate is set to true.

 @Entity
public class CouponEntity {
    @PrimaryKey(autoGenerate = true)
    private int id;
    private String store;
    private String coupon;
    private String expiryDate;
    private String couponCode;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getStore() {
        return store;
    }

    public void setStore(String store) {
        this.store = store;
    }

    public String getCoupon() {
        return coupon;
    }

    public void setCoupon(String coupon) {
        this.coupon = coupon;
    }

    public String getExpiryDate() {
        return expiryDate;
    }

    public void setExpiryDate(String expiryDate) {
        this.expiryDate = expiryDate;
    }

    public String getCouponCode() {
        return couponCode;
    }

    public void setCouponCode(String couponCode) {
        this.couponCode = couponCode;
    }
}
 

Room DAO Interface

You need to define database access methods in DAO interface. Room will generate implementation of DAO interface. You need to tell room to return RxJava observable for queries by specifying return type as one of RxJava observable for query method definition.

Room can return Flowable, Maybe and Single RxJava2 observable types. Flowable emits data when there is data in database and every time data is updated in database. Maybe and Single observables don’t reemit data when data changes in database after complete is called. Single emits one row from database and throws error if there is no record in database. Maybe emits one row if record exists in database.

 @Dao
public interface CouponDAO {
    @Query("SELECT * FROM CouponEntity")
    Flowable<List<CouponEntity>> getCoupons();

    @Query("SELECT * FROM CouponEntity WHERE store = :storeIn ")
    Maybe<CouponEntity> getCouponByStore(String storeIn);

    @Query("SELECT * FROM CouponEntity LIMIT 1")
    Single<CouponEntity> getOneCoupon();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertCoupon(CouponEntity coupon);


    @Query("DELETE FROM CouponEntity")
    void deleteAllCoupons();
}
 

Database

Define database class specifying list of entities and DAOs.

 @Database(entities = {CouponEntity.class}, version = 1)
public abstract class CouponDatabase extends RoomDatabase {
    public abstract CouponDAO couponDao();
}
 

Database Migration

If your app is already using SQLite database using SQLite API, you can continue to use the database with Room. For that, you need to follow Room database migration steps.

Repository Layer

Repository class is just a delegate class which calls dao methods. With this layer it will be easy to replace Room with some other ORM in the future without changing the client by providing new repository implementation.

Interface

 public interface LocalRepository {
    public Flowable<List<CouponEntity>> getCoupons();
    public Maybe<CouponEntity> getCouponByStore(String storeIn);
    public Single<CouponEntity> getOneCoupon();
    public void insertCoupon(CouponEntity coupon);
    public void deleteAllCoupons();
}
 

Implementation

 public class LocalRepositoryImpl implements LocalRepository {
    private CouponDAO couponDAO;
    private Executor executor;

    public LocalRepositoryImpl(CouponDAO cpnDAO, Executor exec) {
        couponDAO = cpnDAO;
        executor = exec;
    }
    public Flowable<List<CouponEntity>> getCoupons() {
        return couponDAO.getCoupons();
    }
    public Maybe<CouponEntity> getCouponByStore(String storeIn) {
        return couponDAO.getCouponByStore(storeIn);
    }
    public Single<CouponEntity> getOneCoupon() {
        return couponDAO.getOneCoupon();
    }
    public void insertCoupon(CouponEntity coupon) {
        executor.execute(() -> {
            couponDAO.insertCoupon(coupon);
        });
    }
    public void deleteAllCoupons() {
        executor.execute(() -> {
            couponDAO.deleteAllCoupons();
        });
    }
}
 

ViewModel

In this example, android architectural component ViewModel is used.

It depends on local repository to access database and remote repository to call remote service to get data.

 package com.zoftino.roomrxjava.viewmodel;


import android.arch.lifecycle.ViewModel;
import android.util.Log;

import com.zoftino.roomrxjava.local.CouponEntity;
import com.zoftino.roomrxjava.remote.CouponsList;
import com.zoftino.roomrxjava.repository.LocalRepository;
import com.zoftino.roomrxjava.repository.RemoteRepository;

import java.util.List;

import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

public class CouponViewModel extends ViewModel {

    private LocalRepository localRepository;

    private RemoteRepository remoteRepository;

    private CompositeDisposable compositeDisposable;

    public CouponViewModel(LocalRepository localRepo, RemoteRepository remoteRepo, CompositeDisposable disposable){
        localRepository = localRepo;
        remoteRepository = remoteRepo;
        compositeDisposable = disposable;
    }

    public Flowable<List<CouponEntity>> getCoupons(){
        return localRepository.getCoupons();
    }

    public Maybe<CouponEntity> getCouponByStore(String storeIn){
        return localRepository.getCouponByStore(storeIn);
    }


    public Single<CouponEntity> getOneCoupon(){
        return localRepository.getOneCoupon();
    }

    public void insertCoupon(CouponEntity coupon){
        localRepository.insertCoupon(coupon);
    }

    public void deleteAllCoupons(){
        localRepository.deleteAllCoupons();
    }

    public void getCouponsFromService(){
        //add observable to CompositeDisposable so that it can be dispose when ViewModel is ready to be destroyed
        //Call retrofit client on background thread and update database with response from service using Room
        compositeDisposable.add(io.reactivex.Observable.just(1)
                .subscribeOn(Schedulers.computation())
                .flatMap(i -> { return remoteRepository.getCoupons();}).subscribeOn(Schedulers.io())
                .subscribe(new Consumer<CouponsList>() {
                    @Override
                    public void accept(CouponsList coupons) throws Exception {
                        for(CouponEntity ce : coupons.getCoupons()){
                            //database update
                            localRepository.insertCoupon(ce);
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.e("MainActivity", "exception getting coupons", throwable);
                    }
                }));

    }
    @Override
    public void onCleared(){
        //prevents memory leaks by disposing pending observable objects
        if (compositeDisposable != null && !compositeDisposable.isDisposed()) {
            compositeDisposable.clear();
        }
    }

}
 

ViewModelFactory

ViewModelFactory is used to inject dependencies into ViewModel.

 @CouponScope
public class CouponViewModelFactory implements ViewModelProvider.Factory {

    @Inject
    LocalRepository localRepository;
    @Inject
    RemoteRepository remoteRepository;
    @Inject @Named("vm")
    CompositeDisposable compositeDisposable;

    @Inject
    public CouponViewModelFactory() {
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (modelClass.isAssignableFrom(CouponViewModel.class)) {
            return (T) new CouponViewModel(localRepository, remoteRepository, compositeDisposable);
        }
        throw new IllegalArgumentException("Wrong ViewModel class");
    }
}

Activity

In the activity onCreate method, ViewModel is instantiated and remote service method is called, which runs on the background thread and updates database with data from remote service response.

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

    CouponApplication.getComponent(getApplicationContext())
            .getCouponComponent(new CouponModule(getApplicationContext())).inject(this);

    //instantiate view model
    couponViewModel = ViewModelProviders.of(this, couponViewModelFactory).get(CouponViewModel.class);

    //call retrofit service to get latest data and update database
    //runs in the background thread
    couponViewModel.getCouponsFromService();

    //recyclerView to show list of data items from database
    couponRecyclerView = (RecyclerView) findViewById(R.id.coupon_rv);
    RecyclerView.LayoutManager couponLayoutManager = new LinearLayoutManager(this);
    couponRecyclerView.setLayoutManager(couponLayoutManager);
}

In onStart method, getCoupons() is called on view model to get Flowable which emits list of rows from database. This call is made on the background thread and consumer gets results on the main thread. The records will be displayed in RecyclerView. If data is updated in the database, Flowable will emit the changes and changes will be displayed in UI.

 @Override
protected void onStart() {
    super.onStart();

    compositeDisposable.add(couponViewModel.getCoupons()
            .subscribeOn(Schedulers.computation())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<List<CouponEntity>>() {
                @Override
                public void accept(List<CouponEntity> coupons) throws Exception {
                    if(coupons != null) {
                        CouponsAdapter ca = new CouponsAdapter(coupons, MainActivity.this);
                        couponRecyclerView.setAdapter(ca);
                    }
                }
            }, new Consumer<Throwable>() {
                @Override
                public void accept(Throwable throwable) throws Exception {
                    Log.e("MainActivity", "exception getting coupons");
                }
            }));
}

Similarly, database access methods which return Maybe and Single observable are called in response to user events.

Observables should be added to CompositeDisposable so that pending observable objects can be disposed when activity is ready to be destroyed. This step prevents memory leak.

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

You can view complete activity code available on Git at MainActivity.java

Dagger Configuration

In this example, all the dependencies are injected to target classes using dagger. There are two modules for two scopes, application level and coupon activity level scopes.

Provided methods which inject objects like retrofit client, and executor...etc, which are used throughout the application are defined in the application module. Provided methods for coupon scope objects are defined in different module.

Coupon component is defined as sub component so that application component and coupon component objects can be injected to target classes.

AppComponent exposes coupon component.

 @Singleton
@Component(modules={AppModule.class})
public interface AppComponent {
 CouponComponent getCouponComponent(CouponModule couponModule);
}
 

Coupon component with inject method that can access parent component objects.

 @CouponScope
@Subcomponent(modules = CouponModule.class)
public interface CouponComponent {
    void inject(MainActivity mainActivity);
}
 

Complete example is available on git at https://github.com/srinurp/RoomRxJava