[Android - Kotlin] Jetpack Compose - 9

민채·2024년 3월 5일
0

Android - Codelab

목록 보기
9/10

Room

  • 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리
  • 테이블을 @Entity 주석이 달린 데이터 클래스로 정의
  • @ColumnInfo 주석이 달린 속성을 테이블의 열로 정의
  • DAO를 @Dao 주석이 달린 인터페이스로 정의, DAO는 Kotlin 함수를 데이터베이스 쿼리에 매핑
  • SQL 문을 작성하지 않고도 간단한 삽입, 삭제, 업데이트를 실행하는 메서드를 정의할 수 있도록 @Insert, @Delete, @Update와 같은 편의성 주석을 제공
  • 좀 더 복잡한 삽입, 삭제, 업데이트 작업을 정의해야 하거나 데이터베이스의 데이터를 쿼리해야 하는 경우에는 @Query 사용

구성요소

  • Room Database : 앱에 해당 데이터베이스와 연결된 DAO 인스턴스를 제공하는 데이터베이스 클래스
  • Data Access Objects (DAO) : 앱이 데이터베이스에서 데이터를 검색, 업데이트, 삽입, 삭제하는 데 사용하는 메서드를 제공
  • Entities : 앱 데이터베이스의 테이블을 나타냄 -> 이를 사용하여 테이블의 행에 저장된 데이터를 업데이트하고 삽입할 새 행을 만듦

사용법

build.gradle에 종속성 추가

//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 클래스는 테이블을 정의하고 이 클래스의 각 인스턴스는 데이터베이스 테이블의 행을 나타냄
  • @Entity 사용
@Entity(tableName = "items") // 테이블 이름 설정
data class Item(
    @PrimaryKey // 기본 키로 설정
    val id: Int = 0,
    val name: String,
    val price: Double,
    val quantity: Int
)

DAO 만들기

  • 추상 인터페이스를 제공하여 지속성 레이어를 애플리케이션의 나머지 부분과 분리하는 데 사용할 수 있는 패턴
  • 데이터베이스 작업 실행과 관련된 모든 복잡성을 숨기기 위해 사용
  • 데이터베이스에 액세스하는 인터페이스를 정의하는 Room의 기본 구성요소

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>>

DB 인스턴스 만들기

  • Entity 및 DAO를 사용하는 RoomDatabase를 만듦
  • @Database 사용
    • entities : 테이블 이름 넣기
    • version : 초기 버전 1, 스키마를 업데이트 할 때마다 번호를 높여야함
    • exportSchema : 스키마 버전 기록 백업을 유지 여부

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())
    }
}

저장 기능 추가

  • 앱의 일시적인 데이터를 저장하고 데이터베이스에도 액세스하려면 ViewModel을 업데이트해야 함
  • ViewModel은 DAO를 통해 데이터베이스와 상호작용하여 UI에 데이터를 제공
  • 모든 데이터베이스 작업은 기본 UI 스레드에서 벗어나 실행되어야 합니다 -> 코루틴과 viewModelScope를 사용하면 됨

Codelab - Room 저장 기능 추가

데이터 읽기

  • ItemDao에 getItem()getAllItems()를 사용 (Flow를 반환)
  • ViewModel에서 Flow를 노출할 때 권장되는 방법은 StateFlow를 사용하는 것입니다
  • StateFlow를 사용하면 UI 수명 주기와 관계없이 데이터를 저장하고 관찰할 수 있습니다
  • FlowStateFlow로 변환하려면 stateIn 연산자를 사용
    • stateIn의 매개변수
      • scope : viewModelScopeStateFlow의 수명 주기를 정의, viewModelScope가 취소되면 StateFlow도 취소
      • started : 파이프라인은 UI가 표시되는 경우에만 활성화해야 함 -> SharingStarted.WhileSubscribed()를 사용하면 됨
      • initialValue : 상태 흐름의 초깃값을 HomeUiState()로 설정
  • FlowStateFlow로 변환한 후에는 collectAsState() 메서드를 사용하여 State로 변환할 수 있음

Codelab - DB 데이터 읽기, 수정

DataStore

저번에 정리해놓은 DataStore 정리 글을 참고!

DateStore 정리

사용법

내가 정리해놓은 글과 다른 점은 ViewModel에서 DataStore를 사용한다는 것이다
해당 방법은 코드랩에서 보고 똑같이 따라했기 때문에 링크를 남긴당...ㅎ

Codelab - DataStore ViewModel에서 사용

참조

profile
코딩계의 떠오르는 태양☀️

0개의 댓글