Data fetch from API not showing on recyclerview MVVM kotlin - kotlin

I got blocked for this problem, Api will call and get the objects from the response and it will display on the recyclerview. But it is not showing,
fetchProductCategories will do the api call.
prepareProducts will handle the fetched from fetchProductCategories
Fragment:
#AndroidEntryPoint
class ProductsWelcomeFragment : BaseProductsFragment<ProductsWelcomeViewModel, ProductsWelcomeFragmentBinding>() {
private val TAG = "ProductsWelcomeFragment"
#Inject
lateinit var animationQueue: AnimationQueue
override fun getViewModelClass(): KClass<ProductsWelcomeViewModel> = ProductsWelcomeViewModel::class
override fun getContentViewRes(): Int = R.layout.products_welcome_fragment
private val cordovaViewModel: CordovaViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* Initialize only once when everytime accessing products screen.
* */
sharedViewModel.initialSetUpRequest()
viewModel.fetchProductCategories()
}
override fun onBindView() {
with(dataBinding) {
viewModel = this#ProductsWelcomeFragment.viewModel
title = getString(R.string.products_screen_welcome_header)
recyclerViewProducts.configure()
executePendingBindings()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.productCategoriesLiveData.observe(viewLifecycleOwner) {
Log.i(TAG, it.toString())
viewModel.items.observe(viewLifecycleOwner) {
viewModel.prepareProducts()
}
}
viewModel.showProgress.observe(viewLifecycleOwner) {}
viewModel.failedAtRetrievingData.observe(viewLifecycleOwner) {
// showErrorScreen(true)
}
}
private fun RecyclerView.configure() {
with(dataBinding.recyclerViewProducts) {
addItemBindings(SectionHeaderItemViewBinder)
addItemBindings(SpaceItemViewDtoBinder)
addItemBindings(getListButtonItemViewBinder(ProductItem.NormalProduct::dto, ::onProductItemClick))
addItemBindings(getListBigtileItemViewBinder(ProductItem.FeaturedProduct::dto, ::onProductItemClick))
}
}
private fun onProductItemClick(product: ProductItem.NormalProduct) {
when (product.id) {
else -> { // TODO : )}
}
// TODO : implement move to another screen
}
private fun onProductItemClick(product: ProductItem.FeaturedProduct) {
// TODO : implement move to another screen
}
private fun showErrorScreen(isDataRetrievalError: Boolean) {
val dismissAction = ErrorHandlingAction(
getString(R.string.native_done),
null,
null,
ButtonType.PRIMARY
) { dismissDialog ->
dismissDialog.dismiss()
if (isDataRetrievalError) {
findNavController().popBackStack()
}
}
DialogFactory().showBottomDialog(
fragmentManager = parentFragmentManager,
window = requireActivity().window,
title = getString(R.string.native_error_title),
description = getString(R.string.online_identity_something_went_wrong_error_description),
iconId = R.drawable.ic_error_thick_exclamation_icon,
errorHandlingActions = arrayOf(dismissAction),
isHtmlDescription = true
)
}
private fun openCordovaScreen(destination: CordovaPage) {
cordovaViewModel.requestPage(destination)
findNavController().popBackStack(R.id.homeScreenMainFragment, false)
}
}
ViewModel:
#HiltViewModel
class ProductsWelcomeViewModel #Inject constructor(
private val productsRepository: ProductsRepository
) : BaseRequestViewModel(), ViewModelWithItems {
private val TAG = "ProductsWelcomeViewModel"
private val _failedAtRetrievingData = SingleLiveEvent<Boolean>()
val failedAtRetrievingData: LiveData<Boolean> = _failedAtRetrievingData
private val _showProgress = MutableLiveData<Boolean>()
val showProgress: LiveData<Boolean> = _showProgress
private val onProductCategories: SingleLiveEvent<ProductBasketsCategoriesModel?> = SingleLiveEvent()
val productCategoriesLiveData: LiveData<ProductBasketsCategoriesModel?> = onProductCategories
private val _items: MutableLiveData<List<Any>> = MutableLiveData()
override val items: LiveData<List<Any>> = _items
fun fetchProductCategories() {
viewModelScope.launch {
request({ productsRepository.getProductCategories() },
success = { response -> onProductCategories.value = response },
failure = { })
}
}
fun prepareProducts() {
_items.value = mutableListOf<Any>().apply {
productCategoriesLiveData.value?.embedded?.categories?.filter { categories ->
categories.isFeatured == true }?.let { filteredCategories ->
add(HeaderItem(ListSectionHeaderItemViewDto(
text = TextLine(
textRes = if (filteredCategories.isNotEmpty()) {
R.string.products_screen_welcome_header } else { null }
)
)
))
filteredCategories.toItems()?.let { addAll(it) }
}
productCategoriesLiveData.value?.embedded?.categories?.filter { categories ->
categories.isFeatured == false }.let { filteredCategories ->
if (filteredCategories != null) {
add(HeaderItem(ListSectionHeaderItemViewDto(
text = TextLine(
textRes = if (filteredCategories.isNotEmpty()) {
R.string.products_screen_welcome_header } else { null }
)
)
))
}
filteredCategories.toItems()?.let { addAll(it) }
}
}
}
private fun List<CategoriesItemModel>?.toItems(): List<ProductItem>? =
this?.mapIndexed { index, item ->
if (item.isFeatured == true) {
ProductItem.FeaturedProduct(
id = item.id as Any,
name = item.name,
dto = BigTileDto(
title = TextLine(text = item.name),
image = item.id.toString().let { toFeatureIcon(it, item.isFeatured) },
description = TextLine(item.description.toString()),
background = BackgroundType.SINGLE
)
)
} else {
ProductItem.NormalProduct(
id = item.id as Any,
name = item.name,
dto = ListButtonItemViewDto(
firstLine = TextLine(text = item.name),
rightDrawable = R.drawable.ic_arrow_right,
separatorDrawable = R.drawable.list_divider_margin_start_72dp,
background = index.indexToBackgroundType(this.size),
avatarDto = AvatarDto(iconBackgroundColor = R.color.ubs_concrete, avatarSize = AvatarDto.AvatarSize.SMALL_ICON_SIZE, iconId = toFeatureIcon(
item.id, item.isFeatured
)),
)
)
}
}
private fun toFeatureIcon(id: String?, isFeature: Boolean?): Int = if (ProductFeature.verify(id) == true) {
ProductFeature.icon()
} else { if (isFeature == true) { R.drawable.abc_vector_test } else {
R.drawable.balloon_illustration } }
}

Related

Cannot create an instance of class error in Kotlin ViewModel

I am trying to create a ViewModel in my Kotlin app, but I am getting the following error: java.lang.RuntimeException: Cannot create an instance of class com.ri.movieto.presentation.home.HomeViewModel. Also, I am using Hilt.
And here is the code for my HomeFragment:
#AndroidEntryPoint
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val homeViewModel: HomeViewModel by viewModels()
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
contentLayout = binding.contentLayout
dataLoading = binding.dataLoading
subscribeToObservables()
return binding.root
}
private fun subscribeToObservables() {
lifecycleScope.launchWhenStarted {
homeViewModel.state.collectLatest {
Log.e("++++", it.response?.page.toString())
}
}
}
}
Here is the code for my HomeViewModel:
#HiltViewModel
class HomeViewModel #Inject constructor(
private val getTrendingMoviesUseCase: GetTrendingMoviesUseCase
) : ViewModel() {
private val _state = MutableStateFlow(TrendingMoviesState())
val state = _state.asStateFlow()
init {
getTrendingMovies()
}
private fun getTrendingMovies() {
getTrendingMoviesUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
_state.value = TrendingMoviesState(response = result.data)
}
is Resource.Error -> {
_state.value =
TrendingMoviesState(error = result.message ?: "An unexpected error occurred")
}
is Resource.Loading -> {
_state.value = TrendingMoviesState(isLoading = true)
}
}
}
}
}
GetTrendingMoviesUseCase.kt:
class GetTrendingMoviesUseCase #Inject constructor(
private val repository: MovieRepository,
private val movieResponseDtoToDomain: MovieResponseDtoToDomain
) {
operator fun invoke(): Flow<Resource<MovieResponse>> = flow {
try {
emit(Resource.Loading())
val movieResponseDto = repository.getTrendingMovies()
val movieResponse = movieResponseDtoToDomain.mapFrom(movieResponseDto)
emit(Resource.Success(movieResponse))
} catch (e: HttpException) {
emit(Resource.Error(e.localizedMessage ?: "Beklenmeyen bir hata oluştu"))
} catch (e: IOException) {
emit(Resource.Error("Lütfen internet bağlantınızı kontrol edin"))
}
}
}
MovieResponseDtoToDomain.kt:
#Singleton
class MovieResponseDtoToDomain #Inject constructor(private val itemDecider: MovieItemDecider) :
Mapper<MovieResponseDto, MovieResponse> {
override fun mapFrom(input: MovieResponseDto): MovieResponse {
return MovieResponse(
page = input.page,
movies = input.results.map { movie ->
MovieResponse.Movie(
release_year = itemDecider.provideReleaseYear(movie.release_date),
poster_path = itemDecider.providePosterPath(movie.poster_path),
backdrop_path = itemDecider.provideBackdropPath(movie.backdrop_path),
vote_average = itemDecider.provideRoundedAverage(movie.vote_average),
title = movie.title,
id = movie.id,
video = movie.video,
release_date = movie.release_date,
overview = movie.overview,
genre_ids = movie.genre_ids
)
},
total_pages = input.total_pages,
total_results = input.total_results
)
}
}

Loading state adapter is always loading

My task is to load recyclerView and show pagination which I both implemented.
I also implemented LoadingState for adapter.
ApiCall:
#GET("top-headlines?sources=bbc-news,techcrunch&apiKey=${BuildConfig.API_KEY}")
suspend fun getTopHeadlinesArticles(
#Query("page") page:Int = 1,
#Query("q") query: String,
) : Response<ArticleListResponse>
I wont show paging because it is working so I will jump to repository:
fun getSearchResult(query: String) =
Pager(
config = PagingConfig(
pageSize = 1,
maxSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = { ArticlePaging(newsService, query)}
).flow
ViewModel:
#OptIn(ExperimentalCoroutinesApi::class)
val news = _currentQuery.flatMapLatest { query ->
articleRepository.getSearchResult(query).cachedIn(viewModelScope)
}
Fragment:
binding.recyclerViewTop.layoutManager = LinearLayoutManager(context)
binding.recyclerViewTop.adapter = adapter.withLoadStateHeaderAndFooter(
header = ArticleLoadStateAdapter { adapter.retry() },
footer = ArticleLoadStateAdapter { adapter.retry() }
)
lifecycleScope.launchWhenCreated {
viewModel.news.collect { articles ->
adapter.submitData(articles)
}
}
And LoadStateViewHolder in LoadStateAdapter:
init {
binding.buttonRetry.setOnClickListener {
retry.invoke()
}
}
fun bind(loadState: LoadState) {
binding.apply {
progressBar.isVisible = loadState is LoadState.Loading
buttonRetry.isVisible = loadState !is LoadState.Loading
textViewError.isVisible = loadState !is LoadState.Loading
}
}
I already predefined DEFAULT_QUERY so I only get 1 Article.
Problem is that progressBar loading is always visible and I have no more articles to show.
Edit: when i have at least 10 items to show this works fine
Edit 2nd: Add ArticlePagingSource if that can help
override fun getRefreshKey(state: PagingState<Int, ArticleResponse>): Int? {
return state.anchorPosition?.let {
val anchorPage = state.closestPageToPosition(it)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
try {
val currentPageList = params.key ?: 1
response = if(query != ""){
newsService.getTopHeadlinesArticles(currentPageList, query)
} else{
newsService.getTopHeadlinesArticles(currentPageList)
}
val responseList = mutableListOf<ArticleResponse>()
val data = response.body()?.articleResponses ?: emptyList()
responseList.addAll(data)
val prevKey = if (currentPageList == 1) null else currentPageList - 1
return LoadResult.Page(
responseList,
prevKey,
currentPageList.plus(1)
)

Observing live data from an API is not updating ui when data changes

I am trying to develop a football app demo. Data comes from an API from the api
It loads data as expected when app started, but when score of match changes, ui is not updating for scores by itself. I am using DiffUtil getChangePayload() to detect changes in score and status fields of Match objects which comes from the response. But it is not triggering when live match data changes. What am i missing?
P.S. I put layout in SwipeRefreshLayout and when i refresh, it gets scores and update the ui. But i want to see the match status and scores updating by itself.
Here is my code:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday: MutableLiveData<List<Matche>> = MutableLiveData()
init {
getMatchesToday()
}
fun getMatchesToday() = viewModelScope.launch {
safeMatchesToday()
}
private suspend fun safeMatchesToday() {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
matchesToday.postValue(it.matches)
}
}
}
}
}
class MatchesTodayFragment : Fragment() {
private var _binding: FragmentMatchesTodayBinding? =null
private val binding get() = _binding!!
private lateinit var mMatchesAdapter: MatchesAdapter
private val viewModel: MatchesViewModel by viewModels {
MatchesViewModelFactory(requireActivity().application, (requireActivity().application as MatchesApplication).repository)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
viewModel.matchesToday.observe(viewLifecycleOwner) { matches ->
mMatchesAdapter.differ.submitList(matches)
}
binding.srlMatchesToday.setOnRefreshListener {
viewModel.getMatchesToday()
binding.srlMatchesToday.isRefreshing = false
}
}
}
class MatchesAdapter(val fragment: Fragment): RecyclerView.Adapter<MatchesAdapter.ViewHolder>() {
private val differCallback = object: DiffUtil.ItemCallback<Matche>() {
override fun areItemsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.status == newItem.status &&
oldItem.score.fullTime.home == newItem.score.fullTime.home &&
oldItem.score.fullTime.away == newItem.score.fullTime.away &&
oldItem == newItem
}
override fun getChangePayload(oldItem: Matche, newItem: Matche): Any? {
val bundle: Bundle = bundleOf()
if (oldItem.status != newItem.status) {
bundle.apply {
putString(Constants.MATCH_STATUS, newItem.status)
}
}
if (oldItem.score.fullTime.home != newItem.score.fullTime.home) {
bundle.apply {
putInt(Constants.HOME_SCORE, newItem.score.fullTime.home)
}
}
if (oldItem.score.fullTime.away != newItem.score.fullTime.away) {
bundle.apply {
putInt(Constants.AWAY_SCORE, newItem.score.fullTime.away)
}
}
if (bundle.size() == 0) {
return null
}
return bundle
}
}
val differ = AsyncListDiffer(this, differCallback)
#SuppressLint("UseCompatLoadingForDrawables")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val match = differ.currentList[position]
holder.apply {
Glide.with(fragment)
.load(match.homeTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivHomeTeamImage)
Glide.with(fragment)
.load(match.awayTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivAwayTeamImage)
tvHomeTeamName.text = match.homeTeam.name
tvAwayTeamName.text = match.awayTeam.name
when (match.status) {
Constants.TIMED -> {
tvMatchTime.text = Constants.toTimeForTR(match.utcDate)
tvHomeTeamScore.text = "-"
tvAwayTeamScore.text = "-"
}
Constants.PAUSED -> {
tvMatchTime.text = Constants.FIRST_HALF
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
Constants.FINISHED -> {
tvMatchTime.text = Constants.FINISHED
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
else -> {
tvMatchTime.text = Constants.IN_PLAY
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val item = payloads[0] as Bundle
val status = item.getString(Constants.MATCH_STATUS)
val homeScore = item.getInt(Constants.HOME_SCORE)
val awayScore = item.getInt(Constants.AWAY_SCORE)
holder.apply {
tvMatchTime.text = status
tvHomeTeamScore.text = homeScore.toString()
tvAwayTeamScore.text = awayScore.toString()
Log.e("fuck", status.toString())
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
LiveData only pushes new values if you command it to. Since you want to do it repeatedly, you need to create a loop. This is very easy to do using the liveData coroutine builder.
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
emit(it.matches)
}
}
}
delay(5000) // however many ms you want between fetches
}
}
}
If this is a Retrofit response, I think checking isSuccessful is redundant because body() will be non-null if and only if isSuccessful is true. So it could be simplified a bit from what you have:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
repository.getMatchesToday()?.body()?.matches?.let(::emit)
}
delay(5000) // however many ms you want between fetches
}
}
}

How to parse two data list from one PagingData Source in Android Kotlin?

This is My ViewModle Class
#HiltViewModel
class MainViewModel #Inject constructor(
private val movieRepository: MovieRepository,
private val favMovieRepository: FavMovieRepository
) : ViewModel() {
...
private var _movieState = mutableStateOf(false)
val movieState = _movieState
val nowPlayingMovies: Flow<PagingData<Movie>> = Pager(PagingConfig(pageSize = 10)) {
MoviePagingSource(movieRepository)
}.flow
val popularMovies: Flow<PagingData<Movie>> = Pager(PagingConfig(pageSize = 10)) {
MoviePagingSource(movieRepository)
}.flow
...
fun setListToPopular(){
_movieState.value = true
}
}
PagingSource file is here
class MoviePagingSource(
private val movieRepository: MovieRepository
) : PagingSource<Int , Movie>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
return try {
val nextPage = params.key ?: 1
val nowPlayingMovieResponse = movieRepository.getNowPlayingMovies(nextPage)
val popularMovieResponse = movieRepository.getPopularMovies(nextPage)
LoadResult.Page(
data = nowPlayingMovieResponse.results,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = nowPlayingMovieResponse.page.plus(1)
)
LoadResult.Page(
data = popularMovieResponse.results,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = popularMovieResponse.page.plus(1)
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
...
}
Here I define two LoadResult one for NowPlaying other for popluar movies.
I want to get the follwing result as expected Below.
Here whenever i choose one result should be in my favour for that i have my Home screen.
#Composable
fun MovieListView(viewModel: MainViewModel) {
val lazyMovieItems = viewModel.nowPlayingMovies.collectAsLazyPagingItems()
val lazyPopularMovieItems = viewModel.popularMovies.collectAsLazyPagingItems()
val movieItems = if (!viewModel.movieState.value) lazyMovieItems else lazyPopularMovieItems
LazyColumn {
item {
Row(... ) {
PopularityDropDown(){
viewModel.setListToPopular()
}
} }
items(movieItems) { item ->
MovieCard(movie = item!!) {
...
)
}else Log.d(TAG, "MovieListView: ${item.original_title} with id ${item.id} It is Not favorite ")
}
}
movieItems.apply {
...
}
}
What should be the way for fetching data from PagingSource ? and please suggest better way for handling NowPlaying and popular movie.

Problem saving data into room db from paging library api response

I have an application built using Jetpack Compose , where i also use paging library 3 to fetch data from db , i have multiple remote mediator where i fetch data and save it directly into database , the issue is that sometimes data gets saved , sometimes not , it goes to the point that sometimes one of the two only gets data stored.
Remote Mediator 1:
#ExperimentalPagingApi
class PopularClothingRemoteMediator #Inject constructor(
private val clothingApi: ClothingApi,
private val clothingDatabase: ClothingDatabase
) : RemoteMediator<Int, Clothing>(){
private val clothingDao = clothingDatabase.clothingDao()
private val clothingRemoteKeysDao = clothingDatabase.clothingRemoteKeysDao()
override suspend fun initialize(): InitializeAction {
val currentTime = System.currentTimeMillis()
val lastUpdated = clothingRemoteKeysDao.getRemoteKeys(clothingId = 1)?.lastUpdated ?: 0L
val cacheTimeout = 1440
val diffInMinutes = (currentTime - lastUpdated) / 1000 / 60
return if (diffInMinutes.toInt() <= cacheTimeout) {
// Log.d("RemoteMediator", "UP TO DATE")
InitializeAction.SKIP_INITIAL_REFRESH
} else {
// Log.d("RemoteMediator", "REFRESH")
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(loadType: LoadType, state: PagingState<Int, Clothing>): MediatorResult {
return try {
val page = 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 = clothingApi.getPopularClothing(page = page)
if (response.popularClothing.isNotEmpty()) {
clothingDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
clothingDao.deleteAllClothing()
clothingRemoteKeysDao.deleteAllRemoteKeys()
}
val prevPage = response.prevPage
val nextPage = response.nextPage
val keys = response.popularClothing.map { clothing ->
ClothingRemoteKeys(
clothingId = clothing.clothingId,
prevPage = prevPage,
nextPage = nextPage,
lastUpdated = response.lastUpdated
)
}
// When i debug this code , it works fine and the last line is executed
// the issue data sometimes gets saved , sometimes not
clothingRemoteKeysDao.addAllRemoteKeys(clothingRemoteKeys = keys)
clothingDao.addClothing(clothing = response.popularClothing)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextPage == null)
} catch (e: Exception) {
return MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.clothingId?.let { clothingId ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothingId)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
}
Remote Mediator 2:
class OuterwearRemoteMediator #Inject constructor(
private val clothingApi: ClothingApi,
private val clothingDatabase: ClothingDatabase
) : RemoteMediator<Int, Clothing>() {
private val clothingDao = clothingDatabase.clothingDao()
private val clothingRemoteKeysDao = clothingDatabase.clothingRemoteKeysDao()
override suspend fun initialize(): InitializeAction {
val currentTime = System.currentTimeMillis()
val lastUpdated = clothingRemoteKeysDao.getRemoteKeys(clothingId = 1)?.lastUpdated ?: 0L
val cacheTimeout = 1440
val diffInMinutes = (currentTime - lastUpdated) / 1000 / 60
return if (diffInMinutes.toInt() <= cacheTimeout) {
// Log.d("RemoteMediator", "UP TO DATE")
InitializeAction.SKIP_INITIAL_REFRESH
} else {
// Log.d("RemoteMediator", "REFRESH")
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(loadType: LoadType, state: PagingState<Int, Clothing>): MediatorResult {
return try {
val page = 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 = clothingApi.getOuterwear(page = page)
if (response.outerwear.isNotEmpty()) {
clothingDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
clothingDao.deleteAllClothing()
clothingRemoteKeysDao.deleteAllRemoteKeys()
}
val prevPage = response.prevPage
val nextPage = response.nextPage
val keys = response.outerwear.map { clothing ->
ClothingRemoteKeys(
clothingId = clothing.clothingId,
prevPage = prevPage,
nextPage = nextPage,
lastUpdated = response.lastUpdated
)
}
// the same thing here
// When i debug this code , it works fine and the last line is executed
// the issue data sometimes gets saved , sometimes not
clothingRemoteKeysDao.addAllRemoteKeys(clothingRemoteKeys = keys)
clothingDao.addClothing(clothing = response.outerwear)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextPage == null)
} catch (e: Exception) {
return MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Clothing>): ClothingRemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.clothingId?.let { clothingId ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothingId)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, Clothing>): ClothingRemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}