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 holder4. 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
| Component | Mô tả |
|---|---|
@Entity | Table definition |
@Dao | Data access methods |
@Database | Database holder |
@Query | Custom SQL queries |
@Insert/@Update/@Delete | CRUD operations |
@TypeConverter | Custom type conversion |
@Relation | Entity relationships |
Last updated on