I am trying to implement paging for the TMDB API using paging3 and paging-compose. Here the source of truth is database and api calls are handled by Remote-mediator.
Repository:
class Repository @Inject constructor(
val database: Database,
private val apiService: ApiService
){
@ExperimentalPagingApi
fun movies(): Flow<PagingData<Movie>> = Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = MovieRemoteMediator(database,apiService),
){
database.movieDao().pagedTopRated()
}.flow
}
RemoteMediator:
@ExperimentalPagingApi
class MovieRemoteMediator(
private val database: Database,
private val networkService: ApiService
) : RemoteMediator<Int, Movie>() {
override suspend fun load(loadType: LoadType, state: PagingState<Int, Movie>): MediatorResult {
val page:Int = when(loadType){
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevKey = remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
prevKey
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextKey = remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
nextKey
}
}
return try {
val response
: MovieResponse = networkService.getTopRatedMovies(page)
val toInsert: MutableList<Movie> = mutableListOf();
for (i in response.results)
toInsert.add(i.mapToMovie());
val endOfPaginationReached = response.page + 1 > response.totalPages
database.withTransaction {
if (loadType == LoadType.REFRESH) {
database.movieKeyDao().clearRemoteKeys()
database.movieDao().clearMovies()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = response.results.map {
MovieKey(movieId = it.id, prevKey = prevKey, nextKey = nextKey)
}
database.movieDao().insertMovies(toInsert)
database.movieKeyDao().insertAll(keys)
}
MediatorResult.Success(
endOfPaginationReached = endOfPaginationReached
)
} catch (e: IOException) {
MediatorResult.Error(e)
} catch (e: HttpException) {
MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Movie>): MovieKey? {
// Get the last page that was retrieved, that contained items.
// From that last page, get the last item
return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { repo ->
// Get the remote keys of the last item retrieved
database.movieKeyDao().remoteKeysMovieId(repo.id)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Movie>): MovieKey? {
// Get the first page that was retrieved, that contained items.
// From that first page, get the first item
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { repo ->
// Get the remote keys of the first items retrieved
database.movieKeyDao().remoteKeysMovieId(repo.id)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Movie>
): MovieKey? {
// The paging library is trying to load data after the anchor position
// Get the item closest to the anchor position
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { repoId ->
database.movieKeyDao().remoteKeysMovieId(repoId)
}
}
}
ViewModel:
@HiltViewModel
class MovieViewModel @Inject constructor(
private val movieRepository: Repository
) : ViewModel() {
@ExperimentalPagingApi
fun getMovies() = movieRepository.movies().cachedIn(viewModelScope)
}
Ui Screen:
@ExperimentalCoilApi
@ExperimentalPagingApi
@Composable
fun MainScreen(){
val viewModel: MovieViewModel = viewModel()
val movieList = viewModel.getMovies().collectAsLazyPagingItems()
LazyColumn{
items(movieList){ movie ->
if (movie != null) {
Card(movie)
}
}
}
}
@ExperimentalCoilApi
@ExperimentalPagingApi
@Composable
fun Main(){
MainScreen()
}
MainActivty.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@ExperimentalCoilApi
@ExperimentalPagingApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposePagingTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Main()
}
}
}
}
}
I am following the paging3 Codelab for writing the remoteMediator. On opening the app, it only loads the first 2 pages and then loops infinitely making infinite retrofit calls