Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create and use a Room Database in Kotlin [Dagger-Hilt]

This is a self-answered question which I intended to ask earlier as I had rep exposure issues in my project, but ended up fixing it after a couple of hours of research. Instead of staying silent, I thought this might help someone in the future. This tutorial demonstrates how you can create a Room database and use it within an activity/fragment. The example use case given here is querying the size of your database and updating the views in a fragment.

Note: There is some Dagger-Hilt dependency injection in the code that follows, but the same methodology should apply if you're manually doing your own dependency injection. I also expect that you have some basic knowledge of MVVM architecture. You may find useful Java related questions here if you're interested in other ways to do this involving LiveData: resource 1, resource 2; however, the focus is on Kotlin and this solution doesn't require LiveData.

like image 353
jsonV Avatar asked Jul 29 '20 04:07

jsonV


People also ask

Does Dagger work with Kotlin?

Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android.

What is difference between Hilt and Dagger?

In Dagger, we create scope annotations such as ActivityScope, FragmentScope to specify the lifecycle, but hilt provides us with core components such as Application, Activity, Fragment, Service, and View.


1 Answers

You'll have to relate the kotlin files in your project for however your project's packages are structured, but the imports should stay the same. In this case, I'm using Dagger-Hilt for dependency injection to avoid boilerplate code.

ItemsYouAreStoringInDB.kt

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "items")
data class ItemsYouAreStoringInDB(/*Parameter of Item entry*/) {
    @PrimaryKey(autoGenerate = true)
    var id: Int? = null
}

YourDao.kt

import androidx.room.*
@Dao
interface YourDAO {
    // Other insertion/deletion/query operations

    @Query("SELECT count(id) FROM items") // items is the table in the @Entity tag of ItemsYouAreStoringInDB.kt, id is a primary key which ensures each entry in DB is unique
    suspend fun numberOfItemsInDB() : Int // suspend keyword to run in coroutine
}

YourDatabase.kt

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(
    entities = [ItemsYouAreStoringInDB::class], // Tell the database the entries will hold data of this type
    version = 1
)

abstract class YourDatabase : RoomDatabase() {

    abstract fun getYourDao(): YourDAO
}

Using Dagger-Hilt for dependency injection, YourRepository is able to be created as Dagger-Hilt does stuff under the hood to provide a notificationDao via YourDatabase's abstract fun getYourDao() YourRepository.kt

import path.to.ItemsYouAreStoringInDB
import path.to.YourDAO
import javax.inject.Inject // Dagger-Hilt to allow @Inject constructor

class YourRepository @Inject constructor(
    private val yourDAO: YourDAO
){
    // Other functions from YourDao.kt

    suspend fun numberOfItemsInDB() = yourDAO.numberOfItemsInDB()
}

This isn't a demonstration on how to use Dagger-Hilt, but the following two files would be necessary:

AppModule.kt

import android.content.Context
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import path.to.YourDatabase

import javax.inject.Singleton


@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton // Tell Dagger-Hilt to create a singleton accessible everywhere in ApplicationCompenent (i.e. everywhere in the application)
    @Provides
    fun provideYourDatabase(
        @ApplicationContext app: Context
    ) = Room.databaseBuilder(
        app,
        YourDatabase::class.java,
        "your_db_name"
    ).build() // The reason we can construct a database for the repo

    @Singleton
    @Provides
    fun provideYourDao(db: YourDatabase) = db.getYourDao() // The reason we can implement a Dao for the database

BaseApplication.kt

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp 
class BaseApplication : Application() {}

You would also need to update the AndroidManifest file and select the BaseApplication as the application entry point <application android:name="path.to.BaseApplication" ... to allow Android to take advantage of Dagger-Hilt.

Continuing...

YourViewModel.kt

import dagger.hilt.android.lifecycle.HiltViewModel
import androidx.lifecycle.ViewModel
import path.to.YourRepository

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: YourRepository
): ViewModel() {
    suspend fun databaseSize() : Int {
        return repository.numberOfItemsInDB()
    }
}

Now that your viewmodel can be created and is accessible throughout the entire application as a singleton (no two instances of it can exist), you can use it in a Fragment/Activity. The viewmodel has access to the repository which can receive information by querying the Room database. Here is an example of how you might use this in a fragment:

YourFragment.kt

@AndroidEntryPoint // Dagger-Hilt requirement
class YourFragment : Fragment(R.layout.fragment_yourFragmentName) {
    private val viewModel: MainViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setViewsBasedOnRepo() // You might want to call this in onResume()
    }

    private fun setViewsBasedOnRepo() { 
            GlobalScope.launch(Dispatchers.Main) { // Dispatchers.Main because only the Main thread can touch UI elements. Otherwise you may wish to use Dispatchers.IO instead!
                val size  =
                    withContext(Dispatchers.Default) { viewModel.databaseSize() }
                if (size == 0) { // Do stuff based on an empty database
                    btnAddItemsToDB.visibility = View.VISIBLE
                    textViewWarnNoItemsInDB.visibility = View.VISIBLE
                    recyclerViewItems.visibility = View.INVISIBLE
                } else { // Do other stuff when database has entries of type ItemsYouAreStoringInDB
                    btnAddItemsToDB.visibility = View.INVISIBLE                
                    textViewWarnNoItemsInDB.visibility = View.INVISIBLE
                    rvNotifications.visibility = View.VISIBLE
                }
            }
    }
}
like image 59
jsonV Avatar answered Sep 18 '22 11:09

jsonV