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
}
}
}
Use SimpleXmlConverterFactory in retrofit. And the response class should look like this.