Room.
LocalDataSource
Author:Semen Naduiev
Quick reminder
Database Class and Migrations in Room
The Database Class is the central component of the Room database. It serves as the main entry
point for database interactions and manages database configuration, versioning, and migrations.
Defining a Room Database Class
To create a Room database, you need to define an abstract class that extends RoomDatabase.
This class should be annotated with @Database, specifying:
The entities (tables) that belong to this database.
The database version.
Whether it should be exported for schema versioning.
@Database(entities = [...], version =
X): Defines the tables (entities) and
database version.
abstract class AppDatabase :
RoomDatabase(): The main database
class must extend RoomDatabase
abstract fun userDao(): UserDao: DAO
methods should be defined to provide
access to the database.
Room.databaseBuilder(context,
AppDatabase::class.java, "db_name"):
Initializes the database.
Handling Database Migrations in Room
When you update your database schema (e.g., add/remove columns or tables), you need to handle
migrations to prevent data loss.
Why Use Migrations?
Ensures data is preserved when updating the app.
Prevents users from having to reinstall the app to get the new schema.
Room enforces version control, so migrations are required when schema changes.
Example: Migration from Version 1 to Version 2
Now, in version 2, we want to add an email column.
Let's say in version 1, we had a simple User table:
Handling Multiple Migrations
If you're moving from version 1 → 2 → 3, you need multiple migrations.
For example, if in version 3, we add a notes table:
Auto Migrations in Room
Auto Migration in Room automates schema migrations when upgrading the database without
writing SQL migration scripts manually.
✅ Key Benefits:
● No manual SQL: Room automatically detects schema changes.
● Less error-prone: Reduces migration-related bugs.
● Easier maintenance: Room handles most common schema modifications.
Enabling Auto Migrations in Room
To use Auto Migrations, you must:
● Set up a Room Database class with @Database annotation.
● Define the new version.
● Specify autoMigrations in the @Database annotation.
Handling Complex Auto Migrations
Auto Migrations work only for simple schema changes (adding/removing columns, tables,
foreign keys).
For complex migrations, you need to:
● Use @RenameColumn to rename columns.
● Use @RenameTable to rename tables.
● Provide custom migrations when required.
Using @RenameColumn (Column Renaming)
By default, Room does NOT support renaming columns directly.
To rename a column in Auto Migration, use @RenameColumn.
Using @RenameTable (Table Renaming)
Combining Auto and Manual Migrations in Room
Step 3: Manual Migration (Version 3 - Changing email Type)
Auto migration cannot change column types. We need a manual migration.
Using DB in ViewModel
Використання у Activity
Alternative
Using Android ViewModel(not the best, but good temporary)
Creating Application Class
To use AndroidViewModel you need to create and application class. It’s a start point of your app
and just a singleton.
To add it create a class that inherits from Application() class.
Add android:name to the Manifest file
Combining Flow and Room in Android
Why Use Flow with Room?
Automatic Updates: When data in the database changes, the Flow will emit the updated values.
Asynchronous Execution: Flow runs on a background thread, avoiding UI freezes.
Efficient Data Handling: Flow cancels previous emissions when a new update comes, reducing
unnecessary work.
Defining a DAO with Flow
Using Flow in the Repository
Observing Data in ViewModel
Introduction to Repository Pattern
The Repository Pattern is used to manage data sources in a structured way.
It abstracts the data layer and provides a clean API for the rest of the app.
Helps in separating concerns and enables easier testing.
Why Use the Repository Pattern?
Decouples data sources from business logic.
Provides a single source of truth.
Facilitates switching between local and remote data sources.
Simplifies testing by enabling mock repositories.
Structure of the Repository Pattern
Repository: Acts as a mediator between ViewModel and data sources.
Remote Data Source: Fetches data from an API.
Local Data Source: Stores data in a database or cache.
Implementation of a Repository
class UserRepository(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
){
suspend fun getUser(userId: String): User {
return localDataSource.getUser(userId) ?: remoteDataSource.getUser(userId).also {
localDataSource.saveUser(it)
}
Integration with ViewModel
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _user = MutableStateFlow<User>()
val user: StateFlow<User> get() = _user.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch(Dispatchers.IO) {
_user.value = userRepository.getUser(userId)
}
Repository Pattern