ตัวอย่างการใช้งาน ROOM Database ร่วมกับ Kotlin Flow
บทความนี้จะมาแนะนำการใช้งาน ROOM Database ใน Android โดยที่ประยุกต์การใช้งานร่วมกันกับ Kotlin Flow ที่จะช่วยให้เราสามารถอัพเดตข้อมูลใน UI ได้ทันทีเมื่อมีการเปลี่ยนแปลงข้อมูลใน Database กันครับ
ก่อนจะเข้าสู่เนื้อหา จะมาแนะนำการใช้งาน ROOM Database และการ Setup เบื้องต้นกันก่อน
เริ่มต้นใช้งาน ROOM Database
ทำการเพิ่ม ROOM Database ใน gradle และอย่าลืมเพิ่ม Library ที่เกี่ยวข้อง เช่น Coroutines, lifecycle-viewmodel ด้วย Version ล่าสุด (ณ บทความนี้) เข้าไป
def room_version = "2.2.5"
def coroutines_version = "1.3.4"
def lifecycle_version = "2.2.0"implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
Entity
ก่อนใช้งาน ROOM Database เราจะต้องมี Entity ก่อน ซึ่งก็เปรียบเสมือน table ใน Database นั่นเอง
ทุก field ใน class จะถูกแปลงเป็น column ใน table เราสามารถกำหนดชื่อ column ที่ต้องการได้ด้วยการเพิ่ม annotation ของ field นั้นด้วย @ColumnInfo (name = “column_name”)
และใน Entity จะต้องมี primary key อย่างน้อย 1 field
ตัวอย่างเราจะสร้าง table ชื่อ User ที่เก็บข้อมูล id, username, password_hash, info เราสามารถสร้างออกมาเป็น Entity ได้ดังนี้
@Entity
data class User(
var username: String,
@ColumnInfo(name = "password_hash")
var passwordHash: Int,
var info: String
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
Dao
สเต็ปถัดมาเราต้องสร้าง DAO (Data Access Object) ซึ่งเป็น interface class มี annotation @Dao
; ที่จะเป็นตัวเชื่อมให้เราสามารถเข้าถึงข้อมูล data (@Entity) ใน Database ได้
ตัวอย่าง Dao
Interface ของ UserDao ที่มีการ Insert, Delete, Update, Select ข้อมูลใน Database ดังนี้
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun inserAllUser(userList: List<User>)
@Query("delete from user where id = :id")
suspend fun deleteUser(id: Long): Int
@Query("select * from user where username = :username")
suspend fun getUser(username: String): User
@Query("select * from user order by id asc")
suspend fun getAllUserCoroutine(): List<User>
@Query("select * from user order by id asc")
fun getAllUserFlow(): Flow<List<User>>
@Query("select * from user order by id asc")
fun getAllUserLiveData(): LiveData<List<User>>
@Update()
suspend fun updateUser(user: User)
@Query("delete from user")
suspend fun clearUser()
}
key หลักของเราจะอยู่ตรงนี้ ที่คำสั่ง @Query ที่เราสามารถ return ค่าเป็น Flow ได้เลย
@Query("select * from user order by id asc")
fun getAllUserFlow(): Flow<List<User>>
สร้าง ROOM Database
เมื่อเรามี Entity, Dao ครบแล้ว ต่อมาให้ทำการสร้าง ROOM Database ด้วยคำสั่ง ดังนี้
@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var instance: UserDatabase? = null
private val DATABASE_NAME = "userDatabase"
operator fun invoke(context: Context) =
instance
?: synchronized(this) {
instance
?: buildDatabase(
context
).also {
instance = it
}
}
private fun buildDatabase(context: Context): UserDatabase {
return Room.databaseBuilder(
context,
UserDatabase::class.java,
DATABASE_NAME
).build()
}
}
}
การเพิ่ม, ลบ, และแก้ไขข้อมูล User เข้าไปใน Database ของเราสามารถทำได้โดยตัวอย่างคำสั่งด้านล่าง
fun addMultipleUserToRoom() {
val userList = mutableListOf<User>()
userList.add(ramdomUser())
userList.add(ramdomUser())
userList.add(ramdomUser())
viewModelScope.launch {
UserDatabase(getApplication())
.userDao()
.inserAllUser(userList)
}
}fun addSingleUserToRoom() {
viewModelScope.launch {
UserDatabase(getApplication())
.userDao()
.insertUser(ramdomUser())
}
}fun getSingleUser(userName: String) {
viewModelScope.launch {
UserDatabase(getApplication())
.userDao()
.getUser(userName)
}
}
fun deleteUser(id: Long) {
viewModelScope.launch {
UserDatabase(getApplication())
.userDao()
.deleteUser(id)
}
}
fun clearUser(){
viewModelScope.launch {
UserDatabase(getApplication())
.userDao()
.clearUser()
}
}fun ramdomUser(): User {
val time = Date().time
val user = User(
username = "user$time",
info = "Info$time}",
passwordHash = "111111".hashCode()
)
return user
}
จากนั้นทำการ Observe การเปลี่ยนแปลงข้อมูลของ User ได้ดังนี้
fun observeUser {
UserDatabase(getApplication())
.userDao()
.getAllUserFlow().collect { userList->
if (userList.isNotEmpty()) {
// update UI
}
}
}
หรือเราสามารถแปลงข้อมูล Flow เป็น Live Data ได้เลยเช่นกันด้วยคำสั่ง AsLiveData()
fun observeUser : LiveData<List<User>> {
UserDatabase(getApplication())
.userDao()
.getAllUserFlow()
.asLiveData()
}
แค่เพียงเท่านี้ทุกครั้งที่ข้อมูลของ User มีการเปลี่ยนแปลง Coroutine จะไป trigger บอก Flow ให้ทราบ และทำการส่งข้อมูลล่าสุดมาให้เราใช้งาน เราก็นำค่าที่ได้นี้ไปอัพเดต UI ต่อไป จะเห็นได้ว่า การใช้ Flow มาช่วย observe ข้อมูลจาก ROOM นั้นสามารถใช้งานได้ง่าย และสะดวกเป็นอย่างมาก
สรุป
การใช้งาน ROOM Database ในการเก็บข้อมูลแบบ local หรือ cache เวลาที่เราทำการเพิ่ม ลบ อัพเดตข้อมูล และอยากจะแสดงผลข้อมูลล่าสุดนี้ในหน้าจอ User Interface โดยที่ไม่ต้องไปสั่งให้ ROOM ทำการ Query ข้อมูลมาใหม่ เราสามารถใช้ Flow เข้ามาช่วยในการ Observe ข้อมูล หรือแปลงเป็น Live Data อีกทีก็ได้เช่นกัน ซึ่งจะเห็นว่าช่วยอำนวยความสะดวก ลดขั้นตอนการ implement โค้ดได้มาก และยังทำให้อ่านโค้ดง่ายกระชับขึ้นอีกด้วย และหวังว่าบทความนี้จะเป็นประโยชน์แก่ผู้อ่านไม่มากก็น้อย ขอบคุณครับ.
ที่มา
ตัวอย่าง Source Code