본문 바로가기

안드로이드/Compose Paging3

[안드로이드 Jetpack Compose Paging3 시리즈] RemoteMediator 적용하기 #11


1. RemoteMediator를 Pager에 적용하기

안녕하세요. 지난 포스팅에서는 안드로이드 Jetpack Compose의 Paging3 라이브러리에서 제공하는 RemoteMediator를 구현하고, 코드를 분석하면서 어떤 역할을 하는지에 대해 살펴보았습니다. 이번 포스팅은 RemoteMediator와 PageSource를 Pager에 전달해서 화면에 정상적으로 UI가 보이는지 확인하겠습니다.

캐시 데이터로 영화목록을 불러온 모습
비행기 모드(데이터 사용 X)를 키고 앱을 실행시켰을 때, 캐시 데이터를 사용해서 영화 목록을 보여주는 모습

2. 안드로이드 Jetpack Compose Pager 구현 과정

Pager의 매개변수에 RemoteMediator와 PagingSource를 전달해주어야 합니다. Hilt 라이브러리를 이용해서 의존성을 주입받고, Pager 객체에 적절한 매개변수를 전달해서 RemoteMediator 기능이 적용된 Pager를 반환하는 함수를 MovieRepository 내부에 구현하겠습니다.

2-1. RemoteMediator 의존성 주입하기

MovieRepository에서 MovieRemoteMedaitor를 사용하기 위해서 의존성을 주입하겠습니다. RemoteMediator에서는 네트워크 통신과 로컬 데이터베이스를 모두 해야 하기 때문에 RemoteDataSource와 LocalDataSource를 모두 주입받는 모습입니다.

// DataModule.kt

@Provides
@Singleton
fun provideMovieRepository(
    movieRemote: MovieDataSource.Remote,
    movieLocal: MovieDataSource.Local,
    movieRemoteMediator: MovieRemoteMediator
): MovieRepository {
    return MovieRepositoryImpl(
        remote = movieRemote,
        local = movieLocal,
        remoteMediator = movieRemoteMediator
    )
}

@Provides
@Singleton
fun provideMovieMediator(
    movieLocalDataSource: MovieDataSource.Local,
    movieRemoteDataSource: MovieDataSource.Remote
): MovieRemoteMediator {
    return MovieRemoteMediator(movieLocalDataSource, movieRemoteDataSource)
}

2-2. MovieRepository에서 Paging 데이터 스트림 반환하기

네트워크 통신으로만 영화 목록을 가져오는 movies 함수 및 로컬 데이터베이스와 네트워크 통신 모두 사용하는 moviesUsingRemoteMediator 함수의 모습입니다. PagingSource를 제공하는 부분과 RemoteMediator의 존재에서 차이가 생깁니다.

// MovieRepositoryImpl.kt (Data Layer)

class MovieRepositoryImpl(
    private val remote: MovieDataSource.Remote,
    private val local: MovieDataSource.Local,
    private val remoteMediator: MovieRemoteMediator
) : MovieRepository {
    /**
     * 네트워크 통신만을 사용하여 영화 목록 가져오기
     */
    override fun movies(pageSize: Int): Flow<PagingData<MovieEntity>> {
        return Pager(
            config = PagingConfig(
                pageSize = pageSize,
                enablePlaceholders = false,
            ),
            pagingSourceFactory = { MoviesPagingSource(remote) }
        ).flow
    }

    /**
     * 로컬 데이터베이스와 네트워크 통신을 사용하여 영화 목록 가져오기
     */
    @OptIn(ExperimentalPagingApi::class)
    override fun moviesUsingRemoteMediator(pageSize: Int): Flow<PagingData<MovieEntity>> {
        return Pager(
            config = PagingConfig(
                pageSize = pageSize,
                enablePlaceholders = false,
            ),
            pagingSourceFactory = { local.pagingSource() },
            remoteMediator = remoteMediator
        ).flow.map { pagingData ->
            pagingData.map { it.toDomain() }
        }
    }
}

2-3. Domain Layer에서 moviesUsingRemoteMediator를 호출하도록 변경

기존에는 Domain Layer에서 movies함수를 호출하고 있었습니다. RemoteMediator를 적용한 moviesUsingRemoteMediator를 호출하도록 변경하면 됩니다.

UI Layer에서는 별도의 수정 없이 정상적으로 작동하는 모습을 확인할 수 있습니다.
이렇게 클린 아키텍처의 장점도 확인할 수 있습니다.

// Getmovies.kt (Domain Layer)
class GetMovies (
    private val movieRepository: MovieRepository
){
//    operator fun invoke(pageSize: Int): Flow<PagingData<MovieEntity>> = movieRepository.movies(pageSize)
    operator fun invoke(pageSize: Int): Flow<PagingData<MovieEntity>> = movieRepository.moviesUsingRemoteMediator(pageSize)
}

3. 안드로이드 Jetpack Compose Paging3 시리즈를 마치며

이번 시리즈를 통해 안드로이드 Jetpack Compose의 Paging3 라이브러리 사용 방법과 RemoteMediator를 통한 효율적인 데이터 관리 방법을 알아보았습니다. 

RemoteMediator를 적용하면 네트워크 데이터와 로컬 데이터베이스를 결합하여 사용자에게 더 빠른 데이터 로드 및 좋은 UX를 제공할 수 있습니다.

그러나 RemoteMediator의 구현이 필수는 아니기 때문에 진행하는 프로젝트의 성격에 맞춰서 판단하면 좋을 것 같습니다.