Paging 3 Remote Mediator data is not shown?

109 views Asked by At

I am developing simple News app, and i also implement Remote Mediator functionality in Paging 3 while Developing it i create a News Entity table , NewsRemoteKey table, and DAO for those when i implement and run that the app will not Show any data, i Log the response, But Nothing Happend i doesn't know what wrong i my code!!

So, Please help Me to identify what problem in my approach/code

NewsDao.kt

@Dao
interface NewsDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNews(news: List<NewsDto>)

    @Query("SELECT * FROM news")
    fun getNews(): PagingSource<Int, NewsDto>

    @Query("DELETE FROM news")
    suspend fun clearAllNews()

}
NewsRemoteKeyDao.kt

@Dao
interface NewsRemoteKeyDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertRemoteKey(remoteKey: List<NewsRemoteKey>)

    @Query("SELECT * FROM newsRemoteKey WHERE id =:id")
    suspend fun getAllRemoteKey(id:String):NewsRemoteKey

    @Query("DELETE FROM newsRemoteKey")
    suspend fun clearAllNewsRemoteKey()
}
NewsDto.kt (Model class for Retrofit and for Room Entity)

@Entity(tableName = "news")
data class NewsDto(
    val author: String,
    val content: String,
    val publishedAt: String,
    val title: String,
    @PrimaryKey(autoGenerate = false)
    val url: String,
    val urlToImage: String
)
@Entity(tableName = "news")
data class NewsDto(
    val author: String,
    val content: String,
    val publishedAt: String,
    val title: String,
    @PrimaryKey(autoGenerate = false)
    val url: String,
    val urlToImage: String
)
NewsRemoteKey.kt (Model class for RemoteKeyTable(Entity))

@Entity(tableName = "newsRemoteKey")
data class NewsRemoteKey (
    @PrimaryKey(autoGenerate = false)
    val id:String,
    val prevPage:Int?,
    val nextPage:Int?,
)
NewsDatabase.kt 

@Database(
    entities = [NewsDto::class,NewsRemoteKey::class],
    version = 1
)
abstract class NewsDatabase:RoomDatabase() {

    abstract fun getNewsRemoteKeysDao():NewsRemoteKeyDao
    abstract fun getNewsDao():NewsDao
}
NetworkModule.kt (for hilt di)

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

    @Singleton
    @Provides
    fun provideDatabase(@ApplicationContext context: Context): NewsDatabase {
        return Room.databaseBuilder(
           context= context,
            NewsDatabase::class.java,
            "newsDB"
        ).build()
    }


    @Singleton
    @Provides
    fun provideRetrofitInstance():Retrofit{
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient())
            .build()
    }

    private fun httpClient():OkHttpClient{
        val log = HttpLoggingInterceptor()
        log.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
            .addInterceptor(log)
            .build()

    }

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): NewsApi{
        return retrofit.create(NewsApi::class.java)
    }

}
NewsRemoteMediator.kt (RemoteMediator class)

@ExperimentalPagingApi
class NewsRemoteMediator @Inject constructor(
    private val newsApi: NewsApi,
    private val newsDatabase: NewsDatabase
) : RemoteMediator<Int, NewsDto>() {

    private val getNewsDao = newsDatabase.getNewsDao()
    private val getRemoteKeyDao = newsDatabase.getNewsRemoteKeysDao()

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, NewsDto>
    ): MediatorResult {
        return try {

            val currentPage = when (loadType) {
                LoadType.REFRESH -> {
                    val current = getRemoteKeyClosesToPosition(state)
                    current?.nextPage?.minus(1) ?:1
                }

                LoadType.PREPEND -> {
                    val firstItem = getRemoteKeyForFirstItem(state)
                    val nextPage = firstItem?.prevPage ?: return MediatorResult.Success(
                        endOfPaginationReached =true
                    )
                    nextPage
                }

                LoadType.APPEND -> {
                    val lastItem = getRemoteKeyForLastItem(state)
                    val nextPage = lastItem?.nextPage ?: return MediatorResult.Success(
                        endOfPaginationReached = true
                    ) 
                    nextPage
                }
            }

            val response = newsApi.getBreakingNews(currentPage, "in")

            val endOfPagination = currentPage == response.totalResults

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

            newsDatabase.withTransaction {

                if (loadType == LoadType.REFRESH) {
                    getNewsDao.clearAllNews()
                    getRemoteKeyDao.clearAllNewsRemoteKey()
                }

                val api = response.articles

                val key = api.map {
                    NewsRemoteKey(
                        it.url,
                        prevPage = prevPage,
                        nextPage = nextPage
                    )
                }
                getNewsDao.insertNews(response.articles)
                getRemoteKeyDao.insertRemoteKey(key)

            }

            MediatorResult.Success(endOfPagination)

        } catch (e: Exception) {
            MediatorResult.Error(e)
        }
    }

    private suspend fun getRemoteKeyClosesToPosition(state: PagingState<Int, NewsDto>): NewsRemoteKey? {
        return state.anchorPosition?.let { url ->
            state.closestItemToPosition(url)?.url?.let {
                getRemoteKeyDao.getAllRemoteKey(it)
            }
        }

    }

    private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, NewsDto>): NewsRemoteKey? {
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()?.let {
            getRemoteKeyDao.getAllRemoteKey(it.url)
        }

    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, NewsDto>): NewsRemoteKey? {
        return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()?.let {
            getRemoteKeyDao.getAllRemoteKey(it.url)
        }
    }
}
NewsRepository.kt

class NewsRepository @Inject constructor(
      newsApi: NewsApi,
    private val newsDatabase: NewsDatabase
) {

    @ExperimentalPagingApi
    val newsPagingData = Pager(
    config = PagingConfig(
    pageSize = 20,
    maxSize = 100,
    ), pagingSourceFactory =  {newsDatabase.getNewsDao().getNews()},
        remoteMediator = NewsRemoteMediator(newsApi,newsDatabase)
    ).liveData


}
MainViewModel.kt

@HiltViewModel
class MainViewModel @Inject constructor(private val newsRepository: NewsRepository):ViewModel() {

    @ExperimentalPagingApi
    val page = newsRepository.newsPagingData.cachedIn(viewModelScope)
}
NewsPagingAdapter.kt (PagingStateAdapter for RecyclerView Adapter)


class NewsPagingAdapter @Inject constructor():PagingDataAdapter<NewsDto,NewsPagingAdapter.NewsViewHolder>(COMPARATOR) {

    class NewsViewHolder(binding: NewsItemBinding): RecyclerView.ViewHolder(binding.root){
        val tvTitle = binding.tvNewsTitle
        val tvAuthor = binding.tvAuthor
        val tvPublishedTime = binding.tvPublishedAt
        val newsImage = binding.imgNewsPic
    }


    companion object{
        val COMPARATOR = object : DiffUtil.ItemCallback<NewsDto>(){
            override fun areItemsTheSame(oldItem: NewsDto, newItem: NewsDto)=
                oldItem == newItem

            override fun areContentsTheSame(oldItem: NewsDto, newItem: NewsDto)=
                oldItem.url == newItem.url

        }
    }

    override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        val list = getItem(position)
        holder.apply {
            tvTitle.text = list?.title
            tvAuthor.text = list?.author
            tvPublishedTime.text = list?.publishedAt
            newsImage.load(list?.urlToImage){
                transformations(RoundedCornersTransformation(radius = 60f))
                placeholder(R.drawable.ic_launcher_background)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
        return NewsViewHolder(
            NewsItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
        )
    }
}
MainActivity.kt

@ExperimentalPagingApi
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var adapter: NewsPagingAdapter

    private lateinit var newsRecyclerView:RecyclerView
    private lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        newsRecyclerView = findViewById(R.id.rv_news_list)

        newsRecyclerView.layoutManager = LinearLayoutManager(this)
        newsRecyclerView.setHasFixedSize(true)


        mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]

        mainViewModel.page.observe(this){
            adapter.submitData(lifecycle,it)
            newsRecyclerView.adapter = adapter
        }
    }
}
2

There are 2 answers

1
Alex20280 On

Use SimpleXmlConverterFactory in retrofit. And the response class should look like this.

@Root(name = "rss", strict = false)
data class Rss @JvmOverloads constructor(
    @field:Element(name = "channel")
    var channel: Chennel? = null,
)

@Root(name = "channel", strict = false)
data class Chennel (
    @field:ElementList(entry = "item", inline = true, required = false)
    var items: List<Item>? = null,
)

@Root(name = "item", strict = false)
data class Item (

    @field:Element(name = "title", required = false)
    @param:Element(name = "title", required = false)
    val title: String? = null,

    @field:Element(name = "link", required = false)
    @param:Element(name = "link", required = false)
    val link: String? = null,

    @field:Element(name = "description", required = false)
    @param:Element(name = "description", required = false)
    val description: String? = null
)
0
Nandha Kumar On

PagingDataAdapter listens to internal PagingData loading events as pages are loaded, and uses DiffUtil on a background thread to compute fine grained updates as updated content in the form of new PagingData objects are received.

You have to move newsRecyclerView.adapter = adapter this line outside of the observe since this is responsible for triggering request and updating UI when content gets updated. So, your final code should look like

    mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]
    
    // Set the adapter for the RecyclerView
    newsRecyclerView.adapter = adapter

    // Observe changes in the PagingData and update the adapter accordingly
    mainViewModel.page.observe(this){
        adapter.submitData(lifecycle,it)
    }