Published on

Room trong Android và Các Vấn Đề Thường Gặp

avatar
Name
Kendis
Twitter
@facebook
Room trong Android và Các Vấn Đề Thường Gặp

Room trong Android và Các Vấn Đề Thường Gặp


Cách khai báo và cài đặt

Cài đặt dependencies

def room_version = "2.7.1"

implementation "androidx.room:room-runtime:$room_version"
ksp("androidx.room:room-compiler:$room_version")
implementation "androidx.room:room-ktx:$room_version"

Khai báo các thành phần Room

@Entity
data class User(
    @PrimaryKey val uid: Int,
    val name: String
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    suspend fun getAll(): List<User>

    @Insert
    suspend fun insert(user: User)
}

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

Khởi tạo database

val db = Room.databaseBuilder(
    applicationContext,
    AppDatabase::class.java, "my-database"
).build()

Quan hệ 1-1, 1-n, n-n trong Room

1 - 1: User - Profile

@Entity
data class Profile(
    @PrimaryKey val userId: Int,
    val bio: String
)

data class UserWithProfile(
    @Embedded val user: User,
    @Relation(
        parentColumn = "uid",
        entityColumn = "userId"
    )
    val profile: Profile
)

Get

@Transaction
@Query("SELECT * FROM user WHERE uid = :userId")
suspend fun getUserWithProfile(userId: Int): UserWithProfile

Insert/Update

@Insert suspend fun insertUser(user: User)
@Insert suspend fun insertProfile(profile: Profile)
@Update suspend fun updateProfile(profile: Profile)

1 - N: User - Posts

@Entity
data class Post(
    @PrimaryKey val id: Int,
    val userId: Int,
    val content: String
)

data class UserWithPosts(
    @Embedded val user: User,
    @Relation(
        parentColumn = "uid",
        entityColumn = "userId"
    )
    val posts: List<Post>
)

Get

@Transaction
@Query("SELECT * FROM user WHERE uid = :userId")
suspend fun getUserWithPosts(userId: Int): UserWithPosts

Insert/Update

@Insert suspend fun insertPosts(posts: List<Post>)
@Update suspend fun updatePost(post: Post)

N - N: User - Project qua bảng trung gian

@Entity
data class Project(
    @PrimaryKey val projectId: Int,
    val name: String
)

@Entity(primaryKeys = ["userId", "projectId"])
data class UserProjectCrossRef(
    val userId: Int,
    val projectId: Int
)

data class UserWithProjects(
    @Embedded val user: User,
    @Relation(
        parentColumn = "uid",
        entityColumn = "projectId",
        associateBy = Junction(UserProjectCrossRef::class)
    )
    val projects: List<Project>
)

Get

@Transaction
@Query("SELECT * FROM user WHERE uid = :userId")
suspend fun getUserWithProjects(userId: Int): UserWithProjects

Insert

@Insert suspend fun insertUser(user: User)
@Insert suspend fun insertProject(project: Project)
@Insert suspend fun insertUserProjectCrossRef(ref: UserProjectCrossRef)

Migration - Nâng cấp schema

Tạo migration thủ công

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

Thêm vào database builder

Room.databaseBuilder(
    context,
    AppDatabase::class.java, "my-database"
).addMigrations(MIGRATION_1_2).build()

Fallback (xoá sạch dữ liệu)

Room.databaseBuilder(
    context,
    AppDatabase::class.java, "my-database"
).fallbackToDestructiveMigration().build()

Lắng nghe thay đổi bằng Flow

Khai báo DAO trả về Flow

@Query("SELECT * FROM user")
fun getAllUsers(): Flow<List<User>>

Trong ViewModel

val usersFlow: Flow<List<User>> = userDao.getAllUsers()

Hoặc lưu vào StateFlow:

private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users

init {
    viewModelScope.launch {
        userDao.getAllUsers().collect {
            _users.value = it
        }
    }
}

Trong Compose

@Composable
fun UserList(viewModel: UserViewModel) {
    val users by viewModel.users.collectAsStateWithLifecycle()

    LazyColumn {
        items(users, key = { it.uid }) { user ->
            Text(user.name)
        }
    }
}

Tổng kết

  • Room giúp thao tác SQLite đơn giản và an toàn hơn
  • Dùng @Relation, @Embedded để xử lý quan hệ giữa các bảng
  • Tự quản lý insert, update, delete trong quan hệ
  • Flow giúp lắng nghe thay đổi dữ liệu theo thời gian thực
  • Luôn dùng @Transaction với truy vấn có @Relation
  • Sử dụng migration cẩn thận để đảm bảo dữ liệu không bị mất