Notice
Recent Posts
Recent Comments
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

성장기록지

Room을 활용한 쇼핑 상품 북마크 기능(Room 구조 구현) 본문

개인 프로젝트

Room을 활용한 쇼핑 상품 북마크 기능(Room 구조 구현)

pengcon 2025. 2. 8. 13:07
쇼핑앱을 구현하며 Room을 통해 로컬 데이터베이스에 상품을 저장하는 기능을 구현하고자 하였다.
이전에 공부한 Room의 구조들을 활용하며, MVVM 구조를 깨지 않도록 구현한 과정을 작성하고자 한다.

 

이전에 내가 학습한 글을 통해 프로젝트 내에 Room의 아키텍쳐대로 구성하고자 하였다.

https://codinghun.tistory.com/23

 

Entity 구성 

Entity는 아래와 같이 구성하였다.

api호출 결과로 ItemsResponse를 가져오고, 

거기서 각 검색 상품마다의 정보를 Item으로 가져왔다.

앱 내에서 ShoppingItem만을 사용하므로 Item의 확장함수를 통해 

아래 코드와 같이 toShoppingItem으로 Entity로 전환해주도록 하였다.

PrimaryKey인 id는 상품의 고유값인 productId를 통해 구성하였다. 

@Entity(tableName = "bookmark_items")
data class ShoppingItem(
    @PrimaryKey val id: String,
    val item: Item,
    val isBookmarked: Boolean
) {
    companion object {
        fun placeholder(): ShoppingItem {
            return ShoppingItem(
                id = "",
                item = Item.placeholder(),
                isBookmarked = false
            )
        }
    }
}

 

아래는 ItemsResponse, Item, toShoppingItem()이 담긴 코드이다.

package com.example.ishopping.data.model

import com.google.gson.annotations.SerializedName

data class ItemsResponse(
    val lastBuildDate: String,
    val total: Int,
    val start: Int,
    val display: Int,
    val items: List<Item>
)

data class Item(
    val title: String,
    val link: String,
    val image: String,
    @SerializedName("lprice") val lowPrice: String,
    @SerializedName("hprice") val highPrice: String?,
    val mallName: String,
    val productId: String,
    val productType: String,
    val brand: String?,
    val maker: String?,
    val category1: String,
    val category2: String,
    val category3: String,
    val category4: String?
) {
    companion object {
        fun placeholder() = Item(
            title = "",
            link = "",
            image = "",
            lowPrice = "",
            highPrice = null,
            mallName = "",
            productId = "",
            productType = "",
            brand = null,
            maker = null,
            category1 = "",
            category2 = "",
            category3 = "",
            category4 = null
        )
    }
}

fun Item.toShoppingItem() = ShoppingItem(
    id = this.productId,
    item = this,
    isBookmarked = false
)

 

 

DAO 구성

아래와 같이 사용할 쿼리문들을 Dao에 지정해두었다.

getAll은 FLow 반환이라 suspend를 붙이지 않았다.

이 글을 작성하며 suspend를 왜 붙여야 하는지 이해하게 되었는데, 현재 프로젝트에는 적용되어있지 않는다.

프로젝트에 적용하며 추가로 글을 작성하도록 하겠다.

@Dao
interface ShoppingItemDao {
    @Insert
    suspend fun insert(shoppingItem: ShoppingItem)

    @Delete
    suspend fun delete(shoppingItem: ShoppingItem)

    @Query("SELECT * FROM bookmark_items")
    fun getAll(): Flow<List<ShoppingItem>>

    @Query("DELETE FROM bookmark_items")
    suspend fun deleteAllBookmarkItems()
}

 

DATABASE 구성

의존성 주입을 위해 추상클래스로 RoomDatabase를 구성해주었다.

ShopingItemDao에 접근하는 추상 함수도 구현해주었다. 

지금 보니 함수명이 적절하지 않은것 같다.

@Database(entities = [ShoppingItem::class], version = 2)
@TypeConverters(ShoppingItemTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun bookmarkItemDao(): ShoppingItemDao
}

 

이후에 DatabaseModule을 구성하였다.

Room.databaseBuilder를 통해 데이터베이스 인스턴스를 만들고,

provideBookmarkItemDao()를 통해 Dao 인스턴스를 얻어올 수 있다.

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context, AppDatabase::class.java,
            "ishopping-db"
        ).build()
    }
    
    @Provides
    fun provideBookmarkItemDao(appDatabase: AppDatabase) = appDatabase.bookmarkItemDao()
}