Right now am just sitting down by the riverside.Spreading my arms to the open wide. Am lucky to reside in a mountainous town. There are plenty of streams and green vegetation here. I can enjoy nature without spending money. Walking about does provide a good amount of exercising however. What is fascinating me about rivers, and waterfalls in particular is how small they start. They don’t just all over a sudden crash their way to the sea. If you are a beginner developer, just remember that. Move at you own pace. Am saying this because I keep on releasing more and more advanced tutorials and projects and some are being left behind.

Today we look at RxJava2, Room and Clean Architecture. These technologies have become quite popular in android development in the last few years. I want to show you how to create a full quality android application using these technologies.

What is Reactive Programming

Reactive Programming is a form of writing computer programs that react to changes. These changes can be values or events. Some value changes then another part of the app reacts. Likewise an event occurs and another part of the program reacts.

Reactive programming is a general programming term that is focused on reacting to
changes, such as data values or events. It can and often is done imperatively. A call‐
back is an approach to reactive programming done imperatively.

Today we want to give a template project you can modify to create full CRUD apps based on RxJava2, Room and Java Programming Language. Equally important is the fact that we will use proper clean architecture to construct our app. Sure we use MVVM but we do apply a proper design pattern. You will see how to properly organize your projects to achieve clean architecture.

Below are technologies you will learn from this app;

1. Java Programming Language

Yeah we use Java, still the most popular programming language and used widely all over the world.If you are Kotlin developer and aren’t very conversant in Java, then what you are waiting for? Pick this project and master Java.

2. RxJava2

If you are beginner and want to learn how to use RxJava2, you won’t find a better project than this one. Let’s start by using it alongside room and performing our CRUD operations.

3. Clean Architecture

Don’t just create a spaghetti app, with God activities. Let me show you how to implement Clean Architecture for a testable and maintenable app. There are various implementations out there, which is ok, but I think this implementation is better. This is because we basically separate our app in two layers:

  1. Inner Layer – Our domain layer – Contains classes that are at the core of our app and won’t change that often.
  2. Outer Layer : Our Infrastructure Layer – Contains classes that are likely to change from many times during app development.

For a small project, you barely notice any advantage of this separation but for a big project, it becomes obvious.

4. Room

Sooner I will be doing RxJava with the likes of Retrofit and Firebase. For now let’s use Room which is an abstraction over SQLite. Respect Room since you can use it to create a full android application that is usable. The apps are will be offline. However android devices need more of these apps than even the ones connected online.

You will learn how to use Room with RxJava2 and clean architecture. You will learn how to perform all the CRUD operations against SQLite database using Room. In the process we are creating a full usable app that you can even upload to Google Play.

5. Model View ViewModel

Let’s use Model View ViewModel, which I normally use in almost all my apps. It provides great seperation of concerns and Google does recommend it. We make use of Lifecycle extensions classes like AndroidViewModel and LiveData to architecture a fast and well designed app.

6. Single-Page Design

How many times have you come across those apps that don’t beat around the bush, that do exactly what you expect from them and nothing more. Aren’t they convenient? Well learn how to create one youself. These types of apps are more convenient when they don’t have many pages and screens. It’s very possible to create an app which has only a single activity even though it has full functionality. That’s what we create here.

7. Custom Expandable RecyclerView

Using just the normall recyclerview and a normal adapter, we can create an expandable recyclerview. All we need is a component that can have both expanded and collapsed state. We have that in DoubleLift.

Demo

Here is the demo of what we create:

Our Project Structure

Below you can see how we organize our project. We said earlier that we are using Clean Architecture to architect our project. We will have the domain layer and infrastructure layer as the inner and outer layer respectively.

(a). Domain Layer

The Domain layer will comprise our core classes like our model classes:

  1. Lesson : Entity – Our data object.
  2. ICallbacks : Usecase – Will contain our callbacks.
  3. IManager : Usecase – Any Logic for manipulating a single of multiple lessons

1. Lesson.java

This is our main model class. It will represent a single lesson. A lesson will have several properties like:

  • Id
  • Name
  • Date

We will also have a non-persistable property called isExpanded. This property will simply hold for us the expansion/collapse state of our recyclerview itemview.

Here is our Lesson class:

/**
 * Our Lesson Class. We specify the table name in our entity attribute
 */
@Entity(tableName = "LessonsTB")
public class Lesson implements Serializable {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int id;
    @ColumnInfo(name = "lesson")
    private String lesson;
    @ColumnInfo(name = "date")
    private String date;
    private boolean isExpanded = true;

    public int getId() {
        return id;
    }

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

    public String getLesson() {
        return lesson;
    }

    public void setLesson(String lesson) {
        this.lesson = lesson;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public boolean isExpanded() {
        return isExpanded;
    }

    public void setExpanded(boolean expanded) {
        isExpanded = expanded;
    }

}
//end

You’ve seen we’ve used several attributes on this class. They include:

  • @Entity(tableName = "LessonsTB") – To specify our custom table name. Room compiler will be generating for us an sqlite table so we need to supply the table name.
  • @ColumnInfo(name = "id") – To specify the column name. If you annotate a field with the @ColumnInfo attribute, then Room will generate a column for it.
  • @PrimaryKey(autoGenerate = true) -To specify a primary key. It should be a non-null value. If you want Room to autogenerate it for you then you make it an integer and set autogenerate to true.

 

2. Our Use-Cases

Here we will define interfaces and classes to manipulate our Lesson. Let’s start with the ICallbacks interface:

public class ICallbacks {
    /**
     * Our CRUD Methods
     */
    public interface ICrud {
        void insert(final ISaveListener iSaveListener, final Lesson lesson);
        void update(final ISaveListener iSaveListener, final Lesson lesson);
        void delete(final ISaveListener iSaveListener, final Lesson lesson);
        void retrieve(final IFetchListener iFetchListener);
    }
    /**
     * Our DataSave Listener
     * Will be raised once our data is saved.
     */
    public interface ISaveListener {
        void onSuccess(String message);
        void onError(String error);
    }

    /**
     * Our DataLoad Listener
     * Will be raised once our data is loaded. A List of loaded items will
     * passed to us
     */
    public interface IFetchListener {
        void onDataFetched(List<Lesson> lessons);
    }
}

You can see the methods here are abstract. The only have the Lesson class as a dependency. No infrastructure classes are allowed here. This interface is light and will rarely need changing. It can however have several implementations within the infrastructure layer. For example you can implement those CRUD methods for different databases like Realm, SQLIte, MySQL etc. However the essence is that those implementations don’t affect our usecase. The usecase classes are decoupled from the implementations, just like our entity class.

Next we come over to Our IManager class. Our use-cases don’t have to be just interfaces only. We can also have concrete classes as long as they are independent of the infrastructure classes. For example we can have classes that manipulate our entities and return results. These classes need to be independent of the infrastructure classes. Here is our IMananger class:

public class IManager {
    public static List<Lesson> filterByDate(List<Lesson> allLessons, String date){
        ArrayList<Lesson> filteredLessons =new ArrayList<>();

        if(allLessons == null){
            return filteredLessons;
        }
        for (Lesson lesson : allLessons){
            if (lesson != null && lesson.getDate().equalsIgnoreCase(date)){
                filteredLessons.add(lesson);
            }
        }
        return filteredLessons;

    }

}

The above class has a method which will be extremely important for this project. The method will filter our lessons based on a date and return results as a list of lessons for the given date.

(b). Our Infrastructure Layer

This layer will understandably contain 95% of our code. This is implementation specific code and will be subject to numerous changes. However given that we are also using the Model View ViewModel design pattern, we will still organize this layer into decoupled classes and interfaces. Our infrastructure layer will contain the following packages:

  1. data -> db -> { LessonDAO, MyRoomDB}
    -> repository -> {LessonRepository}
  2. viewmodel -> MainViewModel
  3. view -> MainActivity

You can see it’s very clean and classes are well arranged and still testable. Let’s look at these individual classes:

Our Database Classes

We have two of them:

  1. LessonDAO – To define our Room CRUD methods
  2. MyRoomDB – To define our Room Database

To start using Room we will need the following dependencies:
For RxJava:

    //RxJava & RxAndroid
    implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation 'androidx.room:room-rxjava2:2.2.5'

and for Room:

    implementation 'androidx.room:room-runtime:2.2.5'
    annotationProcessor 'androidx.room:room-compiler:2.2.5'

If we weren’t using RxJava then we would have needed only the room and room compiler.However that implies we would have had to manually create async task threads to perform our database operations.

1. LessonDAO

DAO stands for Data Access Object. It’s normally an interface with abstract classes representing our CRUD operations. However these methods do have special annotations that make them interpretable by Room compiler. Thus Room compiler will generate appropriate crud classes based on the annotations that we supply. Here is our LessonDAO interface:

@Dao
public interface LessonDAO {
    //NB= Methods annotated with @Insert can return either void, long, Long, long[],
    // Long[] or List<Long>.
    @Insert
    void insert(Lesson lesson);
    //Update methods must either return void or return int (the number of updated rows).
    @Update
    void update(Lesson lesson);
    //Deletion methods must either return void or return int (the number of deleted rows).
    @Delete
    void delete(Lesson lesson);

    /**
     * method to return our lessons and order them by date.
     * NB/= The Flowable class that implements the Reactive-Streams Pattern
     * and offers factory methods, intermediate operators and the ability
     * to consume reactive dataflows.
     * @return
     */
    @Query("SELECT * FROM LessonsTB ORDER BY date")
    Flowable<List<Lesson>> selectAll();

    //NB= Deletion methods must either return void or return int (the number of deleted
    // rows).
    @Query("delete from LessonsTB")
    void deleteAll();

}
2. MyRoomDB

Our data, we said, will be stored locally in the SQLite database. However these days we have a perfect ORM(Object Relational Mapper)-like abstraction called Room. It makes the process of working with SQLite database much more modern and easier. People no longer write raw SQL statements and work with SQLiteOpenHelper classes directly. It’s too error prone and involves too much boilerplate.

Room moreover has better integrations with modern technologies like Rxjava and there is even an official adapter for Room with RxJava.

    implementation 'androidx.room:room-rxjava2:2.2.5'

Alright our first step is to create an abstract class that extends the RoomDatabase, an abstract class itself:

public abstract class MyRoomDB extends RoomDatabase {

Let’s annotate that class with the following Database attribute:

@Database(entities = {Lesson.class}, version = 2, exportSchema = false)
public abstract class MyRoomDB extends RoomDatabase {

The @Database attribute marks a class as a RoomDatabase.The class should be an abstract class and extend RoomDatabase. The entities specifies the tables in the database. In our case we have only one table, the LessonsTB. If you want more tables all you need is create a POJO class and mark it as a table, the way we did with Lessons class. Everytime you make a change in the table, you will need to increase the database version so that the table schema gets updated. Failure to do so can lead to a crash when you attemp to run the project. If you are encountering crashes, it may be that you have changed the fields in the Lessons class. So come solve it by incrementing the table version.

Now come and define a static MyRoomDB instance:

    private static MyRoomDB myRoomDB;

We will be returning it as our Database instance.

Then to instantiate it:

    /**
     *This factory method will instantiate for us our MyRoomDB class
     * @param c - A Context Object
     * @return
     */
    public static MyRoomDB getInstance(Context c) {
        if (myRoomDB == null) {
            myRoomDB = Room.databaseBuilder(c, MyRoomDB.class,
                    "MyRoomDB")
                    .fallbackToDestructiveMigration()
                    .build();
        }
        return myRoomDB;
    }

The above method is our factory method for this class. It will be returning us instances of MyRoomDB when we need one.

LessonsRepository – Our Repository Class

We will use this class to implement the methods we did define in our ICallbacks interface. We did promise that as those methods were abstract. These methods are basically our CRUD methods. You can provide any implemenation of these methods. For example you can implement them for Retrofit, for Room, for Fast Networking library etc. Take note again that our ICallbacks will remain clean and untouched amidst those changes. We’ve protected it from implementation.

So come we create our LessonsRepository class:

public class LessonsRepository implements ICallbacks.ICrud {

We’ve made it implement the ICrud. This will force us to implement the following methods:

  1. insert() – To insert to our SQLite database.
  2. update() – To update our SQLite database.
  3. delete() – To delete our SQLite database.
  4. select() – To select from our SQLite database.

But first we will need to hold our LessonsDAO as an instance member so that we can easily access it from our methods:

    private LessonDAO lessonDAO;

Then instantiate our Room Database and initialize that LessonDAO:

    public LessonsRepository(Context context) {
        MyRoomDB myRoomDB = MyRoomDB.getInstance(context);
        lessonDAO = myRoomDB.lessonDAO();
    }

Good. Now we are ready for some cool crud stuff. But before that we need some definations as we are about to see some weird terms being used.

RxJava2 Terms and Definitions

Here are some terms you will see in the RxJava code we write and their meanings:

  1. Observable – An abstract class that is the non-backpressured, optionally multi-valued base reactive class that offers factory methods, intermediate operators and the ability to consume synchronous and/or asynchronous reactive dataflows
  2. Flowable – An abstract class that implements the Reactive-Streams Pattern and offers factory methods, intermediate operators and the ability to consume reactive dataflows.
  3. Completable – The Completable class represents a deferred computation without any value but only indication for completion or exception.
    Completable behaves similarly to Observable except that it can only emit either a completion or error signal (there is no onNext or onSuccess as with the other reactive types).
  4. fromAction() – A Completable class method that returns a Completable instance that runs the given Action for each subscriber and emits either an unchecked exception or simply completes.
  5. Action – A functional interface similar to Runnable but allows throwing a checked exception.
  6. Scheduler – A Scheduler is an object that specifies an API for scheduling units of work provided in the form of Runnables to be executed without delay (effectively as soon as possible), after a specified time delay or periodically and represents an abstraction over an asynchronous boundary that ensures these units of work get executed by some underlying task-execution scheme (such as custom Threads, event loop, Executor or Actor system) with some uniform properties and guarantees regardless of the particular underlying scheme.
  7. io() – A Scheduler method that returns a default, shared Scheduler instance intended for IO-bound work.This can be used for asynchronously performing blocking IO.
  8. AndroidSchedulers – Android-specific Schedulers. This is what we are getting from the RxAndroid we had added as a dependency. The other Rx stuff come from RxJava itself.
  9. mainThread() – An RxAndroid method that returns a Scheduler which executes actions on the Android main thread.
  10. observeOn() – A method that receives a Scheduler as a parameter and returns a Completable which emits the terminal events from the thread of the specified scheduler.
  11. subscribeOn() – A method that receives a Scheduler as a parameter and returns a Completable which subscribes the child subscriber on the specified scheduler, making sure the subscription side-effects happen on that specific thread of the scheduler.
  12. subscribe() – A CompletableSource method that subscribes the given CompletableObserver to this CompletableSource instance.
  13. CompletableSource – An interface that represents a basic Completable source base interface, consumable via an CompletableObserver.
  14. CompletableObserver – An interface that provides a mechanism for receiving push-based notification of a valueless completion or an error. When a CompletableObserver is subscribed to a CompletableSource through the CompletableSource.subscribe(CompletableObserver) method, the CompletableSource calls onSubscribe(Disposable) with a Disposable that allows disposing the sequence at any time. A well-behaved CompletableSource will call a CompletableObserver’s onError(Throwable) or onComplete() method exactly once as they are considered mutually exclusive terminal signals.
  15. Disposable – An interface that represents a disposable resource.
  16. onSubscribe() – A CompletableObserver interface method called once by the Completable to set a Disposable on this instance which then can be used to cancel the subscription at any time.
  17. onComplete() – A CompletableObserver interface method Called once the deferred computation completes normally.
  18. onError() – A CompletableObserver interface method called once if the deferred computation ‘throws’ an exception.
  19. Consumer – A functional interface (callback) that accepts a single value.
  20. accept() – A Consumer interface method that Consumes the given value.

RxJava2 + Room CRUD Methods

Here are the CRUD methods you can re-use in your projects that help you perform CRUD operations against Room Database, done reactively using RxJava.

(a). How Insert into Room Database with RxJava2

This method will insert into Room database and provide a sucess or failure message,

    /**
     * Insert into Room database
     * @param dataCallback
     * @param lesson
     */
    @Override
    public void insert(final ICallbacks.ISaveListener dataCallback, final Lesson lesson) {

        Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {
                lessonDAO.insert(lesson);
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }
                    @Override
                    public void onComplete() {
                        dataCallback.onSuccess("INSERT SUCCESSFUL");
                    }
                    @Override
                    public void onError(Throwable e) {
                        dataCallback.onError(e.getMessage());
                    }
                });
    }

Then to implement the method, we just instantiate a Lesson object, pass the properties and insert it. We will do this in our ViewModel class:

    /**
     * This method will:
     * 1. Insert our lesson into our SQLite database
     * @param learnt
     * @param date
     */
    public void insert(ICallbacks.ISaveListener iSaveListener,String learnt, String date) {
        Lesson lesson = new Lesson();
        lesson.setLesson(learnt);
        lesson.setDate(date);
        lessonsRepository.insert(iSaveListener,lesson);
    }

(b). How to Update Room Data using RxJava2

This method will allow us update data in Room database and provide us a success or failure response:

    /**
     * Update our data
     * @param dataCallback
     * @param lesson
     */
    @Override
    public void update(final ICallbacks.ISaveListener dataCallback, final Lesson lesson) {
        Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {
                lessonDAO.update(lesson);
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }
                    @Override
                    public void onComplete() {
                        dataCallback.onSuccess("UPDATE SUCCESSFUL");
                    }
                    @Override
                    public void onError(Throwable e) {
                        dataCallback.onError(e.getMessage());
                    }
                });

    }

Then to implement it in our ViewModel class:

    /**
     * This method will:
     * 1. Update a Lesson
     * @param lesson
     */
    public void update(ICallbacks.ISaveListener iSaveListener,Lesson lesson) {
        lessonsRepository.update(iSaveListener,lesson);
    }

(c). How to Delete data From Room usinsg RxJava2

This method will allow us delete from Room database using RxJava2 and return a success or failure message:

    /**
     * Delete from our Room database
     * @param dataCallback
     * @param lesson
     */
    @Override
    public void delete(final ICallbacks.ISaveListener dataCallback, final Lesson lesson) {
        Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {
                lessonDAO.delete(lesson);
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }
                    @Override
                    public void onComplete() {
                        dataCallback.onSuccess("DELETE SUCCESSFUL");
                    }
                    @Override
                    public void onError(Throwable e) {
                        dataCallback.onError(e.getMessage());
                    }
                });
    }

To implement it:

    /**
     * This method will:
     * 1. Delete a Lesson
     * @param lesson
     */
    public void delete(ICallbacks.ISaveListener iSaveListener,Lesson lesson) {
        lessonsRepository.delete(iSaveListener,lesson);
    }

(d). How to select Data from Room using RxJava2

This method will allow us select all the data stored in Room database and return the list of data:

    /**
     * Select or retrieve our data
     * @param dataCallback
     */
    @Override
    public void retrieve(final ICallbacks.IFetchListener dataCallback) {
        lessonDAO.selectAll().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<List<Lesson>>() {
            @Override
            public void accept(List<Lesson> lessons) throws Exception {
                dataCallback.onDataFetched(lessons);
            }

        });
    }

Then in our ViewModel class we will call this retrieve:

    public void fetchAll(ICallbacks.IFetchListener iFetchListener){
        lessonsRepository.retrieve(iFetchListener);
    }

Our Single Page – MainActivity

Yeah, as we said this is a single page app. We will use lovely dialogs for data entry and editing. For example this method will create for us a beautiful dialog that we can use for both writing new lesson or updating existing one:

    /**
     * This method will:
     * 1. Show our input dialog. We can use this dialog for either update or delete
     * @param forEdit
     * @param lesson
     */
    private void showInputDialog(Boolean forEdit, Lesson lesson) {
        String button2 = "SAVE";
        if (forEdit) {
            button2 = "UPDATE";
        } else {
            button2 = "CREATE";
        }
        new LovelyTextInputDialog(this, R.style.EditTextTintTheme)
                .setTopColorRes(R.color.colorPrimary)
                .setTitle("What did you learn Today?")
                .setIcon(R.drawable.flip_page)
                .setConfirmButton(button2, text ->
                {
                    if (forEdit) {
                        lesson.setLesson(text);
                        lesson.setDate(mv.SELECTED_DATE);
                        mv.update(this,lesson);
                    } else {
                        mv.insert(this,text, mv.SELECTED_DATE);
                    }
                })
                .setNegativeButton(android.R.string.no, null)
                .configureEditText(editText -> {
                    editText.setMinLines(5);
                    if (lesson != null && lesson.getLesson() != null) {
                        editText.setText(lesson.getLesson());
                    }
                })
                .show();
    }

In the demo you guys saw that beautiful horizontal date picker. Here is how we set it up. We start by installing it:

    implementation 'com.github.jhonnyx2012:horizontal-picker:1.0.6'

Then we set it up, setup days,colors etc:

    /**
     * This method will:
     * 2. Setup our horizontal datepicker
     */
    private void setupDatePicker(){
        b.datePicker.setListener(this)
                .setDays(360)
                .setOffset(7)
                .setDateSelectedColor(Color.DKGRAY)
                .setDateSelectedTextColor(Color.WHITE)
                .setMonthAndYearTextColor(Color.DKGRAY)
                .setTodayButtonTextColor(getResources().getColor(R.color.colorPrimary))
                .setTodayDateTextColor(getResources().getColor(R.color.colorPrimary))
                .setTodayDateBackgroundColor(Color.GRAY)
                .setUnselectedDayTextColor(Color.DKGRAY)
                .setDayOfWeekTextColor(Color.DKGRAY)
                .setUnselectedDayTextColor(getResources().getColor(R.color.primaryTextColor))
                .showTodayButton(true)
                .init();
        b.datePicker.setBackgroundColor(Color.LTGRAY);
        b.datePicker.setDate(new DateTime());
    }

We will also need to make our activity implement the DatePickerListener interface:

public class MainActivity extends AppCompatActivity implements DatePickerListener,

Then override the onDateSelected method:

    /**
     * When our datepicker is selected, we obtain the selected date and
     * filter our data
     * @param dateSelected
     */
    @Override
    public void onDateSelected(DateTime dateSelected) {
        String year = String.valueOf(dateSelected.getYear());
        String month = String.valueOf(dateSelected.getMonthOfYear());
        String day = String.valueOf(dateSelected.getDayOfMonth());
        if(dateSelected.getMonthOfYear() < 10){
            mv.SELECTED_DATE = year + "-0" + month + "-" + day;
        }else{
            mv.SELECTED_DATE = year + "-" + month + "-" + day;
        }
        mv.IS_DAILY_VIEW=true;
        mv.fetchAll(this);

    }

Download Project

Follow the link below to download this project. We provide support in case an issue.

Here is the APK demo. Just install it and run.