Room Databases

Room is an Android persistence library that provides an abstraction layer over SQLite. Room makes working with databases relatively easy, even in Kotlin-based Android applications. This detailed lesson will cover:

Sections

  1. Introduction to Room Database
  2. Setting Up Room in Your Project
  3. Creating Entities
  4. Creating a DAO (Data Access Object)
  5. Creating the Database
  6. Using Room in a Repository
  7. Example Application
  8. Testing Room Database

1. Introduction to Room Database

Room is a part of Android Jetpack, and it provides an object-mapping layer over SQLite. It basically shields developers from the pain of database management, making possible operations on the database via Kotlin objects. Room provides compile-time checks of SQLite queries, hence preventing runtime errors.

2. Setting Up Room in Your Project

To use Room, you need to add the necessary dependencies to your  build.gradle file.






Gradle Dependencies



// build.gradle (app level)
dependencies {
    def room_version = \"2.5.0\" // Check for the latest version
    implementation \"androidx.room:room-runtime:$room_version\"
    kapt \"androidx.room:room-compiler:$room_version\" // For Kotlin use kapt
    implementation \"androidx.room:room-ktx:$room_version\" // For Kotlin extensions
}


  • room-runtime: Te basic library with Room API for database management.
  • room-compiler: This is required for compilation of the Room annotations into code at compile time.
  • room-ktx: Kotlin-specific extensions for Room make it easier to work with coroutines.

Make sure you apply the Kotlin Kapt plugin at the top of your build.gradle file:






Gradle Plugin



apply plugin: \'kotlin-kapt\'


3. Creating Entities

User Entity

This data class represents a table in the Room database.






Kotlin Room Entity Example



import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = \"users\") // Specifies the table name
data class User(
    @PrimaryKey(autoGenerate = true) val id: Long = 0, // Primary key with auto-increment
    val name: String, // User\'s name
    val age: Int // User\'s age
)


  • @Entity:This annotation tells Room that this class is an entity; i.e., it corresponds to a table.
  • tableName: The name of the table in the database.
  • @PrimaryKey: defines the id field as the primary key. The autoGenerate = true means Room will auto-increment this value when a new record is inserted.

4. Creating a DAO (Data Access Object)

UserDao

The DAO interface contains methods that Room uses to interact with the database.






Kotlin Room DAO Example



import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
@Dao // Annotation indicating this is a DAO
interface UserDao {
    @Insert // Indicates that this method will insert a User into the database
    suspend fun insertUser(user: User)
    @Query(\"SELECT * FROM users\") // SQL query to select all users
    suspend fun getAllUsers(): List
    @Query(\"SELECT * FROM users WHERE id = :userId\") // SQL query to select a user by ID
    suspend fun getUserById(userId: Long): User?
    @Query(\"DELETE FROM users\") // SQL query to delete all users
    suspend fun deleteAllUsers()
}


  • @Dao: Marks this interface as a DAO.
  • @Insert: Room will create the necessary code to insert a User in the database.
  • @Query: Allows you to define SQL queries to fetch or manipulate data.

5. Creating the Database

AppDatabase

This abstract class defines the database configuration.






Kotlin Room Database Example



import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
@Database(entities = [User::class], version = 1) // Declares entities and version number
abstract class AppDatabase : RoomDatabase {
    abstract fun userDao(): UserDao // Abstract method to access UserDao
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) { // Thread-safe singleton pattern
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    \"app_database\" // Database name
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}


  • @Database: The annotation defines the entities that belong to the database and the version of the database.
  • abstract fun userDao(): UserDao Returns the UserDao that can be used to interact with the DAO methods.
  • Singleton Pattern: This implementation is performed in such a way that, during the app\’s entire lifetime, just one instance of the database is ever created, thus preventing memory leak and ensuring thread safety.

6. Using Room in a Repository

UserRepository

The repository pattern abstracts the data sources for better separation of concerns.






Kotlin UserRepository Example



class UserRepository(private val userDao: UserDao) {
    suspend fun insert(user: User) {
        userDao.insertUser(user) // Calls the DAO method to insert a user
    }
    suspend fun getAllUsers(): List<User> {
        return userDao.getAllUsers() // Calls the DAO method to get all users
    }
    suspend fun getUserById(userId: Long): User? {
        return userDao.getUserById(userId) // Calls the DAO method to get user by ID
    }
    suspend fun deleteAllUsers() {
        userDao.deleteAllUsers() // Calls the DAO method to delete all users
    }
}


UserRepository: This class will provide methods for accessing UserDao, hence encapsulating the data operation that will be done, providing easier testing and maintenance.

7. Example Application

MainActivity

This is where we use the ViewModel to interact with the database.






Kotlin MainActivity Example



import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
    private val userViewModel: UserViewModel by viewModels() // ViewModel instance
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Insert a user and fetch all users
        lifecycleScope.launch {
            userViewModel.insert(User(name = \"Alice\", age = 25)) // Inserts a new user
            val users = userViewModel.getAllUsers() // Fetches all users
            users.forEach {
                println(it) // Prints each user to the console
            }
        }
    }
}


  • viewModels(): A property delegate that provides an instance of the ViewModel scoped to the activity.
  • lifecycleScope.launch: Launches a coroutine tied to the lifecycle of the activity. This ensures that the coroutine is canceled if the activity is destroyed.
UserViewModel

This ViewModel handles the data for the UI.






Kotlin UserViewModel Example



import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    fun insert(user: User) {
        viewModelScope.launch {
            repository.insert(user) // Inserts the user using the repository
        }
    }
    suspend fun getAllUsers(): List<User> {
        return repository.getAllUsers() // Retrieves all users from the repository
    }
}


  • ViewModel: It survives configuration changes and is used to store UI-related data.
  • viewModelScope: A CoroutineScope that is automatically canceled when the ViewModel is cleared.
 
ViewModel Factory (Optional)

If your ViewModel requires parameters, you need a factory.






Kotlin UserViewModelFactory Example



import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
    override fun create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            return UserViewModel(repository) as T // Create a UserViewModel instance
        }
        throw IllegalArgumentException(\"Unknown ViewModel class\") // Exception for unknown ViewModel
    }
}


ViewModelProvider.Factory: This interface is implemented to create ViewModel instances that require parameters.

Using the ViewModel Factory in Activity






Kotlin MainActivity Example



class MainActivity : AppCompatActivity() {
    private lateinit var userViewModel: UserViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val userDao = AppDatabase.getDatabase(application).userDao() // Get UserDao from the database
        val userRepository = UserRepository(userDao) // Create UserRepository
        userViewModel = ViewModelProvider(this, UserViewModelFactory(userRepository)).get(UserViewModel::class.java) // Get ViewModel instance
        lifecycleScope.launch {
            userViewModel.insert(User(name = \"Alice\", age = 25)) // Insert a user
            val users = userViewModel.getAllUsers() // Fetch all users
            users.forEach {
                println(it) // Print users
            }
        }
    }
}


8. Testing Room Database

UserDaoTest

This test class verifies the functionality of the DAO.






Kotlin UserDaoTest Example



import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
class UserDaoTest {
    private lateinit var database: AppDatabase
    private lateinit var userDao: UserDao
    @Before
    fun setup() {
        database = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            AppDatabase::class.java
        ).build() // Creates an in-memory database for testing
        userDao = database.userDao() // Gets the UserDao
    }
    @After
    fun teardown() {
        database.close() // Closes the database after tests
    }
    @Test
    fun testInsertAndGetUser() = runBlocking {
        val user = User(name = \"Alice\", age = 25) // Create a test user
        userDao.insertUser(user) // Insert the user into the database
        val users = userDao.getAllUsers() // Retrieve all users
        assertEquals(users.size, 1) // Check that one user exists
        assertEquals(users[0].name, \"Alice\") // Check that the user\'s name is correct
    }
}


  • JUnit Annotations:

    • @Before: Runs before each test, setting up the in-memory database.
    • @After: Cleans up by closing the database after each test.
    • @Test: Marks a function as a test case.
  • runBlocking: Runs a coroutine that blocks the current thread, allowing for the testing of suspend functions.

Conclusion

In this lesson, we have gone through the process with a full example of how to implement Room Database in a Kotlin Android application. Every part—starting from Entity and DAO to Repository and ViewModel—plays its role in maintaining data efficiently while keeping the app responsive.

This setup aids not only in managing the data but provides a clear structure by maintaining the principles of separation of concerns and architecture best practices. Any further questions or anything, you would like me to elaborate on, concerning the code above?.

Scroll to Top