본문 바로가기

안드로이드/Compose Paging3

[안드로이드 Jetpack Compose Paging3 시리즈] Room Database 세팅하기 #7


1. RemoteMediator 구현을 위해 Room Database 세팅

안녕하세요. 이번 포스팅에서는 Jetpack Compose Paging3 라이브러리에서 RemoteMediator를 구현하기 위해 Room Database 세팅을 진행하겠습니다. 지난 포스팅에서 설명했듯이, RemoteMediator는 Paging3 라이브러리로 불러온 데이터를 로컬 데이터베이스에 저장한다고 말씀드렸습니다. 그래서 먼저 Room Database를 세팅한 후, RemoteMediator를 구현해야 합니다.

Room Database 세팅을 위한 파일 생성
Room Database 세팅을 위한 파일 생성

2. Data Layer에 데이터베이스 관련 클래스 및 인터페이스 생성

Data Layer에 Room 데이터베이스 구성을 위한 파일들을 생성하겠습니다. 어렵게 생각하지 말고 간단하게 생각하면 쉽습니다. 데이터베이스가 구성되기 위해서는 Database, Table, DAO, DTO 등이 필요합니다. 저는 에러를 피하고 자동완성을 활용하기 위해 역방향으로 클래스 및 인터페이스를 생성하겠습니다.

아래 코드를 보면 RemoteKey 관련된 파일을 생성한 것을 확인할 수 있는데, 제가 사용하는 API가 page를 파라미터로 받아서 데이터를 돌려주기 때문에 마지막 페이지를 저장하기 위해서 만들었습니다.

만약, page 기반이 아닌 마지막 아이템 ID를 기준으로 페이징 처리를 하는 cursor 기반의 API라면 RemoteKey 저장은 하지 않아도 됩니다. RemoteMediator에서 마지막 아이템을 조회할 수 있기 때문입니다.

2-1. DTO(Data Transfer Object) 만들기

DTO는 계층 간에 데이터를 주고받기 위해서 사용하는 객체입니다. 그래서 DTO는 로직을 갖지 않는 data class 역할만 합니다. Room 데이터베이스에서는 data class에 @Entity 어노테이션을 추가해서 테이블을 생성할 수 있습니다. 여기서는 테이블을 만들 때 사용한 data class가 곧 DTO의 역할도 하게 됩니다.

// MovieDbData.kt
@Entity(tableName = "movies")
data class MovieDbData(
    @PrimaryKey val id: Int,
    val description: String,
    val image: String,
    val backgroundUrl: String,
    val title: String,
    val category: String,
)

fun MovieDbData.toDomain() = MovieEntity(
    id = id,
    title = title,
    description = description,
    image = image,
    category = category,
    backgroundUrl = backgroundUrl
)
// MovieRemoteKeyDbData.kt
@Entity(tableName = "movies_remote_keys")
data class MovieRemoteKeyDbData(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val prevPage: Int?,
    val nextPage: Int?
)

2-2. DAO(Data Access Object) 만들기

DAO는 데이터베이스에 접근해서 데이터를 저장하거나 가져오는 역할을 하는 객체입니다. Room 라이브러리에서는 interface로 정의하면 됩니다. 아래 MovieDao의 pagingSource 함수를 보면 반환타입이 PagingSource인 것을 확인할 수 있습니다. Room에서 PagingSource를 반환하게 되면 Pager 객체가 해당 테이블을 페이징 된 데이터 소스로 사용할 수 있습니다.

// MovieDao.kt
@Dao
interface MovieDao {
    @Query("SELECT * FROM movies ORDER BY category, id")
    fun pagingSource(): PagingSource<Int, MovieDbData>
    
    @Query("SELECT * FROM movies ORDER BY category,id")
    fun getMovies(): List<MovieDbData>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun saveMovies(movies: List<MovieDbData>)
    
    @Query("DELETE FROM movies")
    suspend fun clearMoviesExceptFavorites()
}
// MovieRemoteKeyDao.kt
@Dao
interface MovieRemoteKeyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun saveRemoteKey(keys: MovieRemoteKeyDbData)

    @Query("SELECT * FROM movies_remote_keys WHERE id=:id")
    suspend fun getRemoteKeyByMovieId(id: Int): MovieRemoteKeyDbData?

    @Query("DELETE FROM movies_remote_keys")
    suspend fun clearRemoteKeys()

    @Query("SELECT * FROM movies_remote_keys WHERE id = (SELECT MAX(id) FROM movies_remote_keys)")
    suspend fun getLastRemoteKey(): MovieRemoteKeyDbData?
}

2-3. MovieDatabase 만들기

위에서 생성한 Table, DTO, DAO를 이용해서 Database 클래스를 만들겠습니다. Database도 마찬가지로 @Database 어노테이션을 추가해서 생성할 수 있습니다. 이때, entities 프로퍼티에 위에서 만든 Table인 MovieDbData, MovieRemoteKeyDbData를 넣어줍니다.

// MovieDatabase.kt
@Database(
    entities = [MovieDbData::class, MovieRemoteKeyDbData::class],
    version = 1,
    exportSchema = false
)
abstract class MovieDatabase: RoomDatabase() {
    abstract fun movieDao(): MovieDao
    abstract fun movieRemoteKeyDao(): MovieRemoteKeyDao
}

3. Room Database 내용 정리

이번 포스팅에서는 안드로이드 Jetpack Compose Paging3 라이브러리의 RemoteMediator 구현을 위해서 Room database에 필요한 클래스 및 인터페이스를 정의했습니다.

MovieDao를 보면 PagingSource를 반환하는 pagingSource 함수를 만들었는데, Pager 객체를 생성할 때 pagingSourceFactory로 해당 함수를 제공하면 Paging3 라이브러리가 해당 테이블을 페이징 된 데이터 소스로 사용할 수 있습니다.

다음 포스팅에서는 RemoteMediator를 구현하고 Pager 객체를 생성하겠습니다.