CODEX

Android Tutorial Part 3: Using Room with RxJava 2, Dagger 2, Kotlin and MVVM

Fahri Can
CodeX
Published in
6 min readJun 16, 2020

--

Photo by fabio on Unsplash

This is the third part of the tutorial: Using Room with RxJava 2, Dagger 2, Kotlin and MVVM.

In Part 2 we finished implementing the Room Database.

Now we need a project Application

Go to your root package, which contains the packages data, di, model etc. Create in the root package GiphyApplication.kt.

This class has to extend from android.app.Application. When your class extends from Application, so your class will be alive as long the app is in memory.

class GiphyApplication : Application() {
}

Then head to AndroidManifest.xml and inside the tag<application> add your GiphyApplication as the name. So your app knows this your Application class.

<application
android:name=".GiphyApplication"

When you finished with AndroidManifest.xml head back to GiphyApplication.kt and create two companion objects. One for the GiphyApplication instance and one field for the TrendingDatabase.

companion object {
lateinit var instance: GiphyApplication
lateinit var database: TrendingDatabase
}

Next in the init constructor instantiate your GiphyApplication instance.

init {
instance = this
}

When the application class comes to onCreate() here we want to instantiate the database instance.

override fun onCreate() {
super.onCreate()
database = TrendingDatabase.invoke(this)
}

Again back to the Repository

Remember the TrendingRepository.kt class? We left the class after adding a GiphyApi instance, MutableLiveData objects, LiveData objects and using Dagger 2 dependency injection in the constructor.

Inserting data into the database.

First, we need two more global constants in Constant.kt. Those two constants are for limit of giphies and for the rating, both will be attached to the URL as parameters.

const val LIMIT = "25"
const val RATING = "G"

When this is done, we need a method that inserts the data from the endpoint into the database. Back to TrendingRepository.kt for this method RxJava 2 is needed. Because database operations have to happen on a background thread.

The logic for inserting data into the database is in subscribeToDatabase(). The methods contain three essential sub-methods: onNext(trendingResult: TrendingResult?), onError(t: Throwable?) and onComplete().

Let us start with onNext(trendingResult: TrendingResult?). The first step is to check if the returned object from the endpoint is not null. The second step is to use the method toDataEntityList() from the DataMapper.kt on the list of data. For the returned object from the endpoint to create a new local variable. The third step is to use the database instance of GiphyApplication to insert the local variable from the previous step into the database with the DAO.

But what happens when an error occurs? That’s why we have onError(t: Throwable?). Here comes the MutableLiveData objects _isInProgress and _isError comes handy. Not forget that we also should log an error to the console to make debugging easier.

If no error occurred and onNext(trendingResult: TrendingResult?) is finished we come to onComplete(). Here will be a new method created for getting the data out from the database to display it on the UI.

override fun onComplete() {
getTrendingQuery()
}

Fetching the data out from the database

Here is again GiphyApplication and RxJava 2 needed. As you can remember database operations have to happen on a background thread. First, we need the DAO from the database instance to query the data. Then we need Schedulers.io() for background threading and AndroidSchedulers.mainThread() for displaying the result on the UI. All the logic happens in subscribe().

private fun getTrendingQuery(): Disposable {
return GiphyApplication.database.dataDao()
.queryData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}

onSuccess() we get a List<DataEntity> if the list is not null or empty we pass it to the MutableLiveData<List<Data>> otherwise we have to call the previously created insertData() method.

Okay, now take a short break and look at the gist above from onSuccess(). This is now the most important part of the tutorial! As you can see in else {} the method insertData() gets called. This is is the only place in the entire project where insertData() gets called. We always try first fetching data from the database, only in case, the database is empty then insertData() gets triggered. The first time you install the app and start it and you have Internet connection (to make a GET request to Giphy API) the method insertData() will fire because the local database is empty. Let’s say you kill the app and start it again after a few minutes, a few hours or a few days then every time the data gets directly fetched from the database and insertData() gets never called! This way the local Room database is our single source of truth and we can access the data even offline in flight mode.

onError() we can same logic from insertData(), I would recommend to use another Logcat message.

{
_isInProgress.postValue(true)
Log.e("getTrendingQuery()", "Database error: ${it.message}")
_isError.postValue(true)
_isInProgress.postValue(false)
}

Here is a summary of the method getTrendingQuery():

The last method in TrendingRepository is a public method for the ViewModel to start fetching from the database.

fun fetchDataFromDatabase(): Disposable = getTrendingQuery()

ViewModel the last class to create for MVVM

Go to your root package, which contains the packages data, di, model etc. Create a new package and name it viewmodel. Inside viewmodel create the class TrendingViewModel.kt. This class has to inherit from ViewModel().

class TrendingViewModel: ViewModel() {
}

It is time again for dependency injection

Head back to AppModule here we need a method that provides the repository.

@Provides
fun provideTrendingRepository() = TrendingRepository()

Then of course we need the AppComponent, here we have to tell Dagger 2 that TrendingViewModel needs to be injected.

fun inject(viewModel: TrendingViewModel)

Now we can use the repository inside the ViewModel. We also need a field to store all disposable calls that are made from the repository. In the init {} constructor has to happen the Dagger 2 injection and adding the disposable calls. Don’t forget to clear/remove all disposable calls when the ViewModel gets destroyed by the Android life-cycle.

Finally, time to finish MainActivity

Jump back to AppModule and create a provide method for ArrayList<Data> and TrendingAdapter.

Dagger 2 automatically links the outcome of provideListData() to provideTrendingAdapter(). As you already know it in AppComponent we need the inject method for MainActivity.

fun inject(mainActivity: MainActivity)

Let us again go back to MainActivity, now we can create a field TrendingViewModel and TrendingAdapter. Annotate the TrendingAdapter with @Inject.

@Inject
lateinit var trendingAdapter: TrendingAdapter

private val viewModel: TrendingViewModel by viewModels()

In onCretae() first, use DaggerAppComponent to inject the TrendingAdapter property then create the methods setUpRecyclerView() and observeLiveData().

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

DaggerAppComponent.create().inject(this)

setUpRecyclerView()

observeLiveData()
}

Inside the method setUpRecyclerView() we access the recycler_view (which is the ID of the RecyclerView) from R.layout.activity_main with KAE. There we set the fixed size, itemAnimator and adapter.

private fun setUpRecyclerView() {
recycler_view.apply {
setHasFixedSize(true)
itemAnimator = DefaultItemAnimator()
adapter = trendingAdapter
}
}

Okay, now setUpRecyclerView() is implemented, let us move on to implement observeLiveData(). Inside observeLiveData() create three methods:

private fun observeLiveData() {
observeInProgress()
observeIsError()
observeGiphyList()
}

Start with observeInProgress(). This method is for checking if the data is still loading. If yes, then display ProgressBar, hide the empty text and RecyclerView. Otherwise, just hide the ProgressBar.

The second method observeIsError() just checks if an error occurred while loading the data (like no Internet connection). If yes, we need specific logic that can be reused in the method observeGiphyList(). If it is not an error, the empty text should be disabled and ProgressBar should be enabled.

If an error had occurred. The ProgressBar should be at first visible. Of course the empty text as well. The RecyclerView should be invisible and disabled. The list of the adapter should set to an empty list. In the end, the ProgressBar should be invisible and disabled.

Finally, we arrived at the last method to implement. Hop to observeGiphyList() here the first check is for null or if the list is empty. Then ProgressBar should be at first visible. Making the RecyclerView visible and setting the list of data into the adapter. In the end, the empty text and ProgressBar should be not visible and disabled. In case the list of data is null or an empty list we reuse the logic of disableViewsOnError().

That’s it! Congratulation on marking it so far! I hope this tutorial was useful for you. Here is the completed project, you might checkout branch part3:

Acknowledgments

Special thanks to GIPHY API for providing this free API.

--

--

Fahri Can
CodeX

Android Dev | Blogger | AI & Crypto Enthusiast