Paging3 storing only first fetched values in room database in andorid

Viewed 15

I try to use paging3 in android with offline support and for that I use Remotemediator but i'm not able to store data properly. it's store data only once when first time Api calls after that new item not shown in recyclerview scrolling

MovieDao

    @Dao
    interface MovieDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun addMovies(movies: List<Result>)

    @Query("SELECT * FROM movies")
    fun getMovies(): PagingSource<Int, Result>

    @Query("DELETE FROM movies")
    suspend fun deleteMovies()

    }

RemoteKeyDao

@Dao
interface RemoteKeysDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addAllRemoteKeys(remoteKeys: List<MovieRomoteKeys>)

@Query("SELECT * FROM movieRomoteKeys WHERE id =:id")
suspend fun getRemoteKeys(id: Int): MovieRomoteKeys

@Query("DELETE FROM movieRomoteKeys")
suspend fun deleteAllRemoteKeys()
}

MovieDatabase

@Database(entities = [Result::class, MovieRomoteKeys::class], version = 1)
abstract class MovieDatabase : RoomDatabase() {
   abstract fun movieDao(): MovieDao
   abstract fun remoteKeysDao(): RemoteKeysDao
}

DatabaseModule

@InstallIn(SingletonComponent::class)
@Module
class DataBaseModule {

  @Provides
  @Singleton
  fun provideDatabase(@ApplicationContext appContext: Context): MovieDatabase {
    return Room.databaseBuilder(appContext, MovieDatabase::class.java, 
    "movieDB").build()
   }

}

MovieRemoteMeditor

@ExperimentalPagingApi
class MovieRemoteMediator(
  private val movieApi: MovieApi,
  private val movieDatabase: MovieDatabase
) : RemoteMediator<Int, Result>() {
val movieDao = movieDatabase.movieDao()
val movieRomoteKeysDao = movieDatabase.remoteKeysDao()

override suspend fun load(loadType: LoadType, state: PagingState<Int, Result>): MediatorResult {
    return try {
        val currentPage = when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
                remoteKeys?.nextPage?.minus(1) ?: 1
            }
            LoadType.PREPEND -> {
                val remoteKeys = getRemoteKeyForFirstItem(state)
                val prevPage = remoteKeys?.prevPage ?: return MediatorResult.Success(
                    endOfPaginationReached = remoteKeys != null
                )
                prevPage
            }
            LoadType.APPEND -> {
                val remoteKeys = getRemoteKeyForLastItem(state)
                val nextPage = remoteKeys?.nextPage ?: return MediatorResult.Success(
                    endOfPaginationReached = remoteKeys != null
                )
                nextPage
            }
        }
        val response = movieApi.getMovies(currentPage)
        val endOfPaginationReached = response.total_pages == currentPage

        val prevPage = if (currentPage == 1) null else currentPage - 1
        val nextPage = if (endOfPaginationReached) null else currentPage + 1

        movieDatabase.withTransaction {
            if (loadType == LoadType.REFRESH) {
                movieDao.deleteMovies()
                movieRomoteKeysDao.deleteAllRemoteKeys()
            }
            movieDao.addMovies(response.results!!)
            val keys = response.results.map { movie ->
                MovieRomoteKeys(
                    id = movie.id!!,
                    prevPage = prevPage,
                    nextPage = nextPage
                )
            }
            movieRomoteKeysDao.addAllRemoteKeys(keys)
        }
        MediatorResult.Success(endOfPaginationReached)
    } catch (e: Exception) {
        MediatorResult.Error(e)
    }
}

private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState<Int, Result>): MovieRomoteKeys? {
    return state.anchorPosition?.let { position ->
        state.closestItemToPosition(position)?.id?.let { id ->
            movieRomoteKeysDao.getRemoteKeys(id = id)
        }
    }
}

private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Result>): MovieRomoteKeys? {
    return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()?.let { movie ->
        movieRomoteKeysDao.getRemoteKeys(id = movie.id!!)
    }

}

private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Result>): MovieRomoteKeys? {
    return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
        ?.let { movie -> movieRomoteKeysDao.getRemoteKeys(id = movie.id!!) }
}
}

MovieRepository

@ExperimentalPagingApi
class MovieRepository @Inject constructor(
  private val movieApi: MovieApi,
  private val movieDatabase: MovieDatabase
) {

fun getMovies() = Pager(
    config = PagingConfig(pageSize = 20, maxSize = 100),
    remoteMediator = MovieRemoteMediator(movieApi, movieDatabase),
    pagingSourceFactory = { movieDatabase.movieDao().getMovies() }
).liveData

}

MovieViewModel

 @ExperimentalPagingApi
 @HiltViewModel
 class MovieViewModel @Inject constructor(private val movieRepository: MovieRepository) 
  : ViewModel() {

val list = movieRepository.getMovies().cachedIn(viewModelScope)


 }

MovieListFragment

@ExperimentalPagingApi
@AndroidEntryPoint
class MovieListFragment : Fragment() {

private var _binding: FragmentMovieListBinding? = null
private val binding get() = _binding!!
private val movieViewModel by viewModels<MovieViewModel>()

@Inject
lateinit var moviePagingAdapter: MoviePagingAdapter

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = FragmentMovieListBinding.inflate(inflater, container, false)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initialize()

}

private fun initialize() {
    (activity as AppCompatActivity?)!!.supportActionBar!!.title = "Movie App"

    binding.rvMovies.apply {
        layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        setHasFixedSize(true)
        adapter = moviePagingAdapter.withLoadStateHeaderAndFooter(
            header = LoaderAdapter { moviePagingAdapter.retry() },
            footer = LoaderAdapter { moviePagingAdapter.retry() }
        )
    }

    movieViewModel.list.observe(viewLifecycleOwner, Observer {
        moviePagingAdapter.submitData(lifecycle, it)
    })

    viewLifecycleOwner.lifecycleScope.launch {
        moviePagingAdapter.loadStateFlow.collectLatest { loadStates ->
            binding.shimmerViewContainer.apply {
                startShimmerAnimation()
                isVisible = loadStates.refresh is LoadState.Loading
            }
            binding.layoutError.apply {
                root.isVisible = loadStates.refresh is LoadState.Error
                btnRetry.setOnClickListener {
                    moviePagingAdapter.retry()
                }
            }
        }
    }
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

}
0 Answers
Related