CODEX

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

Fahri Can
CodeX
Published in
6 min readJun 14, 2020

--

Photo by fabio on Unsplash

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

In Part 1 we set up Gradle dependencies, created the models, created an interface for the API endpoint, created the layout for a giphy and created the ViewHolder and Adapter.

It is time for the RecyclerView

Since most Android Developers immediately associate with ViewHolder and Adapter the RecyclerView, I suggest let us continue with that. Create inside the view package a new package and name it ui. Move MainActivity inside the ui package. All information will be displayed on the MainActivity so open the activity_main.xml. The user should be able to see a progress circle while the data is loading. If something went wrong he should see a message which says the list is empty. In conclusion, the following Views and ViewGroups are needed: ConstraintLayout, RecyclerView, TextView, and ProgressBar.

Here is the complete implemented activity_main.xml.

Complete the API GET request

The API call to https://api.giphy.com/v1/gifs/trending is still missing. Head to the network package and create an object class called GiphyApiService.kt.

Next, you will need a method that adds an interceptor to log the HTTP request, sets a connection timeout, and tries to request again on failure.

Before you can continue with the last method, you have to define a constant for the base URL. Go to the package internal and create the file Constant.kt. Inside the file create the constant BASE_URL. Because this is a file and not a class the constants inside this file can be used everywhere in the project. Otherwise we would have something like “FileName.ConstantName”

const val BASE_URL = "https://api.giphy.com/"

Then create an API key on https://developers.giphy.com/ when you have your API key go to the file “.gitgignore”. Add at the end of the file following line:

# Files in the project where version control should not be used
Config.kt

Commit that change in “.gitgignore”

The reason for that was an API key should never be tracked with Git or another version control system! So we can create the file Config.kt and the file will only be locally accessable and will not get pushed to GitHub.

Now head to the package internal and create the file Config.kt. Inside Config.kt create a constant to save the API key from https://developers.giphy.com/

const val KEY = "YOUR API KEY FROM developers.giphy.com"

Time to implement the last method in GiphyApiService.kt. The third method now has to create the Retrofit instance and should use the method createOkHttpClient().

The Repository Pattern

Create a new package and name it repository. Then create a class and name it TrendingRepository.kt. Add an injected field of GiphyApi.

class TrendingRepository {

@Inject
lateinit var giphyApiService: GiphyApi
}

The implementation of the repository will continue after the Dagger 2 @Module and @Component is done.

Dependency injection

Dependency injection allows you to easily separate logic for Unit Testing. The dependency injection library Dagger 2 enables the instantiation of fields and injects them into the right class. All the instantiation (in @Modules) and injecting (in @Component) can be collected in specific classes.

Before continuing with the repository, where GiphyApiService gets used we should use dependency injection for this.

@Module is needed

Create a new package and name it di. Then create a new class called AppModule.kt. Above the class AppModule {} annotate it with @Module.

@Module
class AppModule {

}

Methods in @Module provide something, so start with @Provides then write your method. It is standard that those method names start with provideAnyName(). The return type of the method is very important. Whenever another provideMethod() in AppModule needs an argument to instantiate something. Dagger will look at the return type of the other methods in AppModule. If a correct return type is there, Dagger will automatically link those methods.

Let’s start with an instance of GiphyApiService it would look like this:

@Singleton
@Provides
fun provideApi(): GiphyApi = GiphyApiService.getClient()

Note that the return type is the interface and the actual instance is the service.

Here how AppModule.kt looks right now:

Next step @Component

In the package di create a new Interface called AppComponent.kt. Annotate it with @Singleton and @Component then add parentheses and inside those write:

@Singleton
@Component(modules = [AppModule::class])

The keyword modules will link the AppModule with AppComponent.

Dagger needs to know now, which classes to inject with fields annotated with @Inject. That’s we you have to tell Dagger it should inject the TrendingRepository.kt.

Now you can go back to the TrendingRepository.kt.

Back to the Repository

Before you can use dependency injection in TrendingRepository.kt you have to do these steps:

  1. Click on Build -> Clean Project
  2. Click on Build -> Build Project

For classes that are not Activities or Fragments, just create an init {} block to enable injecting the class. Inside init {} goes following line:

DaggerAppComponent.create().inject(this)

This class DaggerAppComponent was created by Dagger. Notice the word Dagger is always added before the Component interface name.

Next, we need three private MutableLiveData objects and three public LiveData objects which will be needed in the MainActivity.kt. One LiveData object will hold the list of data. One LiveData object will show the progress bar when data is loading. One LiveData object will show the list is empty message when there is no Internet connection for fetching data.

Okay, implementation of TrendingRepository has to be again paused for now, because the database is missing.

The local database

First things first, so we need a table for the database. Create inside the package data new package and name it database. Inside database create the data class DataEntity.kt. Annotate the class with @Entity so Room knows this is a database table. You also have to give the table a unique name, I would name it “data”.

@Entity(tableName = "data")
data class DataEntity (

)

For not violating any rule for database tables we need a primary key. Room enables us to get an autogenerated primary key this will be the id for each row. If you want to know why I set id = 0, click here. The other fields we need are the same as in Data.kt.

Note that the images column is a String and not an Images class. Because you can’t store objects in a table. That's the reason why we need a Mapper right now.

The Mapper

Inside the database package create a new file and name it DataMapper.kt. This file will transform a Data.kt object to a DataEntity.kt and vice versa.

These are basically extension functions for Data.kt and DataEntity.kt

The data access object (DAO)

Inside the database package create a new interface and name it DataDao.kt. Annotate it with @Dao. The DAO class for our purpose inserts and queries the data from the database.

RoomDatabase

Finally, we come to the actual database. Inside the database package create a new abstract class and name it TrendingDatabase.kt. This abstract class needs the @Database annotation and inside parenthesis entities as tables as well as the version of the database. Whenever you add a new entity or modify an existing entity you have to increase the version number. In our case, it is the first version. Or you delete the application on the device, where you want to run the app.

@Database(entities = [DataEntity::class], version = 1)
abstract class TrendingDatabase: RoomDatabase() {

}

Next, you always need a method that is abstract to use the DAO interface.

abstract fun dataDao(): DataDao

After that create a companion object {} which will provide logic to instantiate the database. Inside companion object {} we need @Volatile instance of the database which all threads have immediate access.

@Volatile // All threads have immediate access to this property
private var instance: TrendingDatabase? = null

For safety reasons, we need another instance makes sure no threads making the same thing at the same time.

private val LOCK = Any() // Makes sure no threads making the same thing at the same time

Before continuing jump to the package internal and open again Constant.kt. Create here the constant for the database name.

const val DATABASE_NAME = "trending.db"

Now go back to TrendingDatabase and create the builder method for the database.

private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
TrendingDatabase::class.java,
DATABASE_NAME
).fallbackToDestructiveMigration().build()

The last method which is needed is for creating an instance of the database.

operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also { instance = it }
}

The abstract class TrendingDatabase should look like this:

Okay, that’s it for the second part, here you find the third part: Part 3

Here is the completed project, check out branch part2:

--

--

Fahri Can
CodeX

Android Dev | Blogger | AI & Crypto Enthusiast