I am using paging3 to fetch data from api save it into the database and finally show them to the user. I have seen the wired item count while collecting from the flow :
Here is my Pager config :
override suspend fun getAllMovie(): Flow<PagingData<MovieEntity>> {
val pagingSourceFactory = { appDatabase.movieDao().pagingSource() }
return Pager(config = PagingConfig(pageSize = NETWORK_PAGE_SIZE, enablePlaceholders = false),
remoteMediator = MovieRemoteMediator(mContext,movieRemoteDataSource, appDatabase),
pagingSourceFactory = pagingSourceFactory)
.flow
}
Then in the use case I map it :
class GetMoviesUseCase(private val repository: MovieRepository) : BaseUseCase<PagingData<Movie>> {
val list : MutableList<Movie> = ArrayList<Movie>()
override suspend fun invoke(): Flow<PagingData<Movie>> {
return repository.getAllMovie()
.map { value ->
value.map { entity ->
entity.toMovie()
} }
}
}
After that in ViewModel I cache it :
private fun getAllMovies() {
viewModelScope.launch {
val pagedFlow = getMoviesUseCase()
.cachedIn(scope = viewModelScope)
setState { currentState.copy( pagedData = pagedFlow) }
}
}
Finally in screen, I use it :
var pagingItems: LazyPagingItems<Movie>? = null
viewState.pagedData?.let { flowPage ->
pagingItems = flowPage.collectAsLazyPagingItems()
Log.d("MovieScreen", " Init pagingItems - count: ${pagingItems?.itemCount} ")
}
As you can see I created a log for 'pagingItems - count' . Here as we expected the count should goes up as we scroll the list and get more pages . but for me it's not .
see the logs :
Init pagingItems - count: 0
Init pagingItems - count: 60
Init pagingItems - count: 60
Init pagingItems - count: 20
Init pagingItems - count: 40
Init pagingItems - count: 60
Init pagingItems - count: 80
Init pagingItems - count: 60
Init pagingItems - count: 74
Init pagingItems - count: 60
Init pagingItems - count: 77
Init pagingItems - count: 60
Init pagingItems - count: 80
Init pagingItems - count: 100
Init pagingItems - count: 117
Init pagingItems - count: 60
Init pagingItems - count: 80
Init pagingItems - count: 100
Init pagingItems - count: 105
Init pagingItems - count: 60
For more detail of my code I would add my mediator and the repository for better understanding of my code:
@OptIn(ExperimentalPagingApi::class)
class MovieRemoteMediator(
private val mContext: Context,
private val service: MovieRemoteDataSource,
private val appDatabase: AppDatabase
) : RemoteMediator<Int, MovieEntity>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(loadType: LoadType,
state: PagingState<Int, MovieEntity>): MediatorResult {
Log.d(TAG, "loadType: $loadType")
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: STARTING_PAGE_INDEX
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevKey = remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
Log.d(TAG, "LoadType.PREPEND - prevKey: $prevKey")
prevKey
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextKey = remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
Log.d(TAG, "LoadType.APPEND - nextKey: $nextKey")
nextKey
}
}
try {
val apiResponse = service.getAllMovies(page)
val movieDataList = apiResponse.body()?.results
val endOfPaginationReached = movieDataList?.isEmpty()
appDatabase.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH) {
appDatabase.remoteKeysDao().clearRemoteKeys()
appDatabase.movieDao().clearRepos()
}
val prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPaginationReached == true) null else page + 1
Log.d(TAG, "New Data - nextKey: $nextKey - prevKey: $prevKey ")
movieDataList?.let {
val insertedIds = appDatabase.movieDao().insertAll(it.toMovieList())
val keys = insertedIds.map { resultItem ->
RemoteKeysEntity(movieId = resultItem.toInt(), prevKey = prevKey,
nextKey = nextKey)
}
appDatabase.remoteKeysDao().insertAll(keys)
}
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached == true)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, MovieEntity>
): RemoteKeysEntity? {
// 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 ->
appDatabase.remoteKeysDao().remoteKeysMovieId(repoId)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, MovieEntity>): RemoteKeysEntity? {
// 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 { movie ->
// Get the remote keys of the first items retrieved
appDatabase.remoteKeysDao().remoteKeysMovieId(movie.id)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, MovieEntity>): RemoteKeysEntity? {
// 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
appDatabase.remoteKeysDao().remoteKeysMovieId(repo.id)
}
}
companion object {
private const val STARTING_PAGE_INDEX = 1
private const val TAG = "MovieRemoteMediator"
}
}
And the repository :
class MovieRepositoryImpl @Inject constructor(
private val mContext: Context,
private val movieRemoteDataSource: MovieRemoteDataSource,
private val appDatabase: AppDatabase,
) : MovieRepository {
@OptIn(ExperimentalPagingApi::class)
override suspend fun getAllMovie(): Flow<PagingData<MovieEntity>> {
val pagingSourceFactory = { appDatabase.movieDao().pagingSource() }
return Pager(config = PagingConfig(pageSize = NETWORK_PAGE_SIZE, enablePlaceholders = false),
remoteMediator = MovieRemoteMediator(mContext,movieRemoteDataSource, appDatabase),
pagingSourceFactory = pagingSourceFactory)
.flow
}
}
I would greatly appreciate the assistance of those who can help me overcome my current challenge.
I have checked my paging3 implementation with Google sample in the link, But I can't find the solution.
As Update:
I found that a thing :) In the single source of truth, the database is responsible for serving data into the list. Let's imagine in db we have just 3 pages, So we loaded those 3 pages, Then we have to load more with MovieRemoteMediator. In MovieRemoteMediator after getting a response we insert the new data into db. I think it causes the problem that I have. Because on the other hand, we are observing the database. If changes happen it would change also our list. Here is the code :
try {
val apiResponse = service.getAllMovies(page)
val movieDataList = apiResponse.body()?.results
val endOfPaginationReached = movieDataList?.isEmpty()
appDatabase.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH) {
appDatabase.remoteKeysDao().clearRemoteKeys()
appDatabase.movieDao().clearRepos()
}
val prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPaginationReached == true) null else page + 1
Log.d(TAG, "New Data - nextKey: $nextKey - prevKey: $prevKey ")
movieDataList?.let {
val insertedIds = appDatabase.movieDao().insertAll(it.toMovieList())
val keys = insertedIds.map { resultItem ->
RemoteKeysEntity(movieId = resultItem.toInt(), prevKey = prevKey,
nextKey = nextKey)
}
appDatabase.remoteKeysDao().insertAll(keys)
}
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached == true)