I have following project in Github : https://github.com/AliRezaeiii/News-Cache
First that I load data in my Fragment, I display data from database, and start to load data from API. As soon as loading from API finished, I update the database and my recyclerView in Fragment get updated as well.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.news.observe(viewLifecycleOwner, Observer<List<Article>> { articles ->
articles?.apply {
viewModelAdapter.submitList(articles)
}
})
}
Here is my viewModel :
class NewsViewModel(application: Application) : AndroidViewModel(application) {
private val viewModelJob = SupervisorJob()
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
private val database = getDatabase(application)
private val articleRepository = ArticlesRepository(database)
init {
viewModelScope.launch {
articleRepository.refreshArticles()
}
}
private val _news = articleRepository.articles
val news: LiveData<List<Article>>
get() = _news
}
And here is my Repository class :
class ArticlesRepository(private val database: NewsDatabase) {
/**
* A list of articles that can be shown on the screen.
*/
val articles: LiveData<List<Article>> =
Transformations.map(database.newsDao.getArticles()) {
it.asDomainModel()
}
/**
* Refresh the articles stored in the offline cache.
*
* This function uses the IO dispatcher to ensure the database insert database operation
* happens on the IO dispatcher. By switching to the IO dispatcher using `withContext` this
* function is now safe to call from any thread including the Main thread.
*
* To actually load the articles for use, observe [articles]
*/
suspend fun refreshArticles() {
withContext(Dispatchers.IO) {
try {
val news = Network.news.getNews().await()
database.newsDao.insertAll(*news.asDatabaseModel())
} catch (exception: IOException) {
Timber.e(exception)
}
}
}
}
And here is my Adapter :
class NewsAdapter(val callback: OnClickListener) : ListAdapter<Article, NewsAdapter.NewsViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = NewsViewHolder.from(parent)
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
holder.bind(getItem(position), callback)
}
/**
* Allows the RecyclerView to determine which items have changed when the [List] of [Article]
* has been updated.
*/
companion object DiffCallback : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
}
}
When I start the app(a new instance of application get created) and there is no new data from API, RecyclerView is blinking. Is there any solution to solve it?
Your problem is wrong implementation of DiffCallback, which always return false when areItemsTheSame called even if compared instances represents the same object (as db is giving you new instances every time it is updated).
To fix that you need to compare not if they are the same instance but if they represent the same object. As I can see your Articles have unique urls. So you can use aricle.url or introduce id field and use id.
companion object DiffCallback : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With