@Entity
주석이 달린 데이터 클래스로 정의@ColumnInfo
주석이 달린 속성을 테이블의 열로 정의@Dao
주석이 달린 인터페이스로 정의, DAO는 Kotlin 함수를 데이터베이스 쿼리에 매핑@Insert
, @Delete
, @Update
와 같은 편의성 주석을 제공@Query
사용//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
@Entity
사용@Entity(tableName = "items") // 테이블 이름 설정
data class Item(
@PrimaryKey // 기본 키로 설정
val id: Int = 0,
val name: String,
val price: Double,
val quantity: Int
)
ItemDao.kt
인터페이스로 생성
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
// suspend를 사용해 별도의 스레드에서 실행될 수 있게 함
// 데이터베이스 작업을 실행하는 데는 시간이 오래 걸릴 수 있으므로 별도의 스레드에서 실행해야 함
// 데이터 추가
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
// 데이터 수정
@Update
suspend fun update(item: Item)
// 데이터 삭제
@Delete
suspend fun delete(item: Item)
// Flow를 반환 유형으로 사용하면 데이터베이스의 데이터가 변경될 때마다 알림을 받게 됨
// Room은 이 Flow를 자동으로 업데이트하므로 명시적으로 한 번만 데이터를 가져오면 됨
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
@Database
사용InventoryDatabase.kt
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class InventoryDatabase : RoomDatabase() {
// 데이터베이스가 DAO에 관해 알 수 있도록 ItemDao를 반환하는 추상 함수를 클래스 본문 내에서 선언
abstract fun itemDao(): ItemDao
// 데이터베이스를 만들거나 가져오는 메서드에 대한 액세스를 허용하고 클래스 이름을 한정자로 사용하는 companion object를 정의
companion object {
// 데이터베이스에 관한 null을 허용하는 비공개 변수 Instance를 선언
@Volatile
private var Instance: InventoryDatabase? = null
// 데이터베이스 빌더에 필요한 Context 매개변수를 사용하여 getDatabase() 메서드 정의
fun getDatabase(context: Context): InventoryDatabase {
return Instance ?: synchronized(this) {
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
.build() // DB 인스턴스 만듦
.also { Instance = it } // 최근에 만들어진 db 인스턴스에 대한 참조를 유지
}
}
}
}
ItemsRepository
인터페이스와 OfflineItemsRepository
클래스를 구현하여 데이터베이스에서 get
, insert
, delete
, update
진행
ItemRepository.kt
DAO 구현에 매핑되는 함수를 인터페이스에 추가
import kotlinx.coroutines.flow.Flow
/**
* Repository that provides insert, update, delete, and retrieve of [Item] from a given data source.
*/
interface ItemsRepository {
/**
* Retrieve all the items from the given data source.
*/
fun getAllItemsStream(): Flow<List<Item>>
/**
* Retrieve an item from the given data source that matches with the [id].
*/
fun getItemStream(id: Int): Flow<Item?>
/**
* Insert item in the data source
*/
suspend fun insertItem(item: Item)
/**
* Delete item from the data source
*/
suspend fun deleteItem(item: Item)
/**
* Update item in the data source
*/
suspend fun updateItem(item: Item)
}
OfflineItemsRepository.kt
ItemsRepository 인터페이스에 정의된 함수를 재정의
import kotlinx.coroutines.flow.Flow
class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository {
override fun getAllItemsStream(): Flow<List<Item>> = itemDao.getAllItems()
override fun getItemStream(id: Int): Flow<Item?> = itemDao.getItem(id)
override suspend fun insertItem(item: Item) = itemDao.insert(item)
override suspend fun deleteItem(item: Item) = itemDao.delete(item)
override suspend fun updateItem(item: Item) = itemDao.update(item)
}
AppContainer.kt
데이터베이스를 인스턴스화하고 DAO 인스턴스를 OfflineItemsRepository 클래스에 전달
import android.content.Context
interface AppContainer {
val itemsRepository: ItemsRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
// ItemDao() 인스턴스를 OfflineItemsRepository 생성자에 전달
// Context를 전달하는 InventoryDatabase 클래스에서 getDatabase()를 호출하여 DB 인스턴스를 인스턴스화하고
// .itemDao()를 호출하여 Dao 인스턴스를 만듦
override val itemsRepository: ItemsRepository by lazy {
OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao())
}
}
viewModelScope
를 사용하면 됨getItem()
및 getAllItems()
를 사용 (Flow
를 반환)Flow
를 노출할 때 권장되는 방법은 StateFlow
를 사용하는 것입니다StateFlow
를 사용하면 UI 수명 주기와 관계없이 데이터를 저장하고 관찰할 수 있습니다Flow
를 StateFlow
로 변환하려면 stateIn
연산자를 사용stateIn
의 매개변수scope
: viewModelScope
가 StateFlow
의 수명 주기를 정의, viewModelScope
가 취소되면 StateFlow
도 취소started
: 파이프라인은 UI가 표시되는 경우에만 활성화해야 함 -> SharingStarted.WhileSubscribed()
를 사용하면 됨initialValue
: 상태 흐름의 초깃값을 HomeUiState()로 설정Flow
를 StateFlow
로 변환한 후에는 collectAsState()
메서드를 사용하여 State
로 변환할 수 있음저번에 정리해놓은 DataStore 정리 글을 참고!
내가 정리해놓은 글과 다른 점은 ViewModel에서 DataStore를 사용한다는 것이다
해당 방법은 코드랩에서 보고 똑같이 따라했기 때문에 링크를 남긴당...ㅎ