Skip to Content

Room Database trong Android

1. Giới thiệu

Room là abstraction layer trên SQLite, cung cấp compile-time verification và Kotlin Coroutines support.

2. Setup

// build.gradle.kts plugins { id("com.google.devtools.ksp") } dependencies { implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") ksp("androidx.room:room-compiler:2.6.1") }

3. Components của Room

@Entity ──────► Table trong database @Dao ─────────► Data Access Object (queries) @Database ────► Database holder

4. Entity

@Entity(tableName = "users") data class User( @PrimaryKey(autoGenerate = true) val id: Int = 0, @ColumnInfo(name = "user_name") val name: String, val email: String, @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis() )

Entity với Foreign Key

@Entity( tableName = "posts", foreignKeys = [ ForeignKey( entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE ) ], indices = [Index("user_id")] ) data class Post( @PrimaryKey(autoGenerate = true) val id: Int = 0, @ColumnInfo(name = "user_id") val userId: Int, val title: String, val content: String )

5. DAO (Data Access Object)

@Dao interface UserDao { @Query("SELECT * FROM users") fun getAllUsers(): Flow<List<User>> @Query("SELECT * FROM users WHERE id = :userId") suspend fun getUserById(userId: Int): User? @Query("SELECT * FROM users WHERE user_name LIKE :search") fun searchUsers(search: String): Flow<List<User>> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(user: User): Long @Insert suspend fun insertAll(users: List<User>) @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) @Query("DELETE FROM users") suspend fun deleteAll() @Query("SELECT COUNT(*) FROM users") suspend fun getUserCount(): Int }

6. Database

@Database( entities = [User::class, Post::class], version = 1, exportSchema = true ) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun postDao(): PostDao }

Singleton Pattern

object DatabaseProvider { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "app_database" ) .fallbackToDestructiveMigration() .build() INSTANCE = instance instance } } }

7. Repository Pattern

class UserRepository(private val userDao: UserDao) { val allUsers: Flow<List<User>> = userDao.getAllUsers() suspend fun insert(user: User) = userDao.insert(user) suspend fun update(user: User) = userDao.update(user) suspend fun delete(user: User) = userDao.delete(user) suspend fun getUserById(id: Int) = userDao.getUserById(id) }

8. ViewModel Integration

class UserViewModel(private val repository: UserRepository) : ViewModel() { val allUsers: StateFlow<List<User>> = repository.allUsers .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun addUser(name: String, email: String) { viewModelScope.launch { repository.insert(User(name = name, email = email)) } } fun deleteUser(user: User) { viewModelScope.launch { repository.delete(user) } } }

9. Compose Integration

@Composable fun UserListScreen(viewModel: UserViewModel = viewModel()) { val users by viewModel.allUsers.collectAsState() LazyColumn { items(users, key = { it.id }) { user -> UserItem( user = user, onDelete = { viewModel.deleteUser(user) } ) } } }

10. Type Converters

class Converters { @TypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } @TypeConverter fun fromStringList(value: List<String>): String { return Gson().toJson(value) } @TypeConverter fun toStringList(value: String): List<String> { return Gson().fromJson(value, object : TypeToken<List<String>>() {}.type) } } @Database(...) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase()

11. Migrations

val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE users ADD COLUMN avatar TEXT") } } Room.databaseBuilder(...) .addMigrations(MIGRATION_1_2) .build()

12. Relations

data class UserWithPosts( @Embedded val user: User, @Relation( parentColumn = "id", entityColumn = "user_id" ) val posts: List<Post> ) @Dao interface UserDao { @Transaction @Query("SELECT * FROM users") fun getUsersWithPosts(): Flow<List<UserWithPosts>> }

📝 Tóm tắt

ComponentMô tả
@EntityTable definition
@DaoData access methods
@DatabaseDatabase holder
@QueryCustom SQL queries
@Insert/@Update/@DeleteCRUD operations
@TypeConverterCustom type conversion
@RelationEntity relationships
Last updated on