Reputation: 1071
I am using Firebase firestore pagination and ViewModel class in my project, I have setup an onScroll listener for recyclerview, and fetching data on scroll but when i Navigate to another fragment and back to the main fragment, the whole items are duplicated, How can i fix this issue??
Here is my code
NewsViewModel.kt
class NewsViewModel : ViewModel() {
private val repo = FirebaseRepo(this)
val mutableLiveData = MutableLiveData<List<News>>()
fun getNewsList(tm:Timestamp): LiveData<List<News>> {
repo.getNewsData(tm)
return mutableLiveData
}
}
Repository.kt
class FirebaseRepo(private val viewModel: NewsViewModel) {
private val db = FirebaseFirestore.getInstance().collection("news")
fun getNewsData(tm: Timestamp) {
val newsList = ArrayList<News>()
if(viewModel.mutableLiveData.value != null) {
newsList.addAll(viewModel.mutableLiveData.value!!)
}
db
.orderBy("timestamp", Query.Direction.DESCENDING)
.whereLessThan("timestamp",tm)
.limit(6)
.get()
.addOnSuccessListener {
Log.i("CodeCamp", it.toString())
for (doc in it) {
val imgUrl = doc.getString("imageUrl")
val heading = doc.getString("headline")
val timestamp = doc.getTimestamp("timestamp")
val tagline = doc.getString("tagline")
val type = doc.getString("type")
newsList.add(News(doc.id, imgUrl!!, heading!!, tagline!!, type!!, timestamp!!))
}
viewModel.mutableLiveData.value = newsList
}
}
}
MainActivity.kt
viewModel = ViewModelProvider(this).get(NewsViewModel::class.java)
val layoutManager = LinearLayoutManager(view.context)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = newsAdapter
recyclerView.addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
//observe to the viewModel
viewModel.getNewsList(Timestamp.now()).observe(viewLifecycleOwner, Observer {
newsAdapter.submitList(it)
})
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0
&& totalItemCount >= PAGE_SIZE && !isLoading
) {
isLoading != isLoading
val list = viewModel.mutableLiveData.value!!
viewModel.getNewsList(list[list.size - 1].timestamp).value
Handler().postDelayed({
isLoading != isLoading
},2000)
}
}
})
My Adapter
class NewsAdapter : ListAdapter<News, NewsAdapter.ViewHolder> (NEWS_COMPARATOR) {
companion object {
private val NEWS_COMPARATOR = object : DiffUtil.ItemCallback<News>() {
override fun areItemsTheSame(old: News, new: News): Boolean = old.id == new.id
override fun areContentsTheSame(old: News, new: News): Boolean = old == new
}
}
class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bindView(news: News) {
Glide.with(view).load(news.imageUrl).into(itemView.img)
itemView.news_title.text = news.heading
itemView.news_src.text = news.tagline
itemView.news_type.text = news.type
itemView.news_time.text = DateTime.getTimeAgo(news.timestamp.seconds)
itemView.setOnClickListener {
it.findNavController().navigate(R.id.action_homeFragment_to_newsFragment, bundleOf("id" to news.id))
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_news,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val news = getItem(position)
holder.bindView(news)
}
}
Upvotes: 0
Views: 1509
Reputation:
The problem is in your destructuring:
Everytime Observer called your ViewModel is re-fetching data from firebase and holding it in variable called mutableLivedata as you defined.
You need to observe mutableLiveData for your recyclerView and call getNewsItem() inside init function as shown below:
ViewModel.kt
val mutableLiveData = MutableLiveData<List<News>>()
fun getNewsList(tm:Timestamp) {
repo.getNewsData(tm)
}
init {
getNewsList(Timestamp.now())
}
MainActivity.kt
viewModel.mutableLiveData.observe(viewLifecycleOwner, Observer {
newsAdapter.submitList(it)
})
Happy Coding..
Upvotes: 1
Reputation: 796
LiveData is designed to hold the data and every time you subscribe to it returns the data it currently has. Once you come back to your fragment the data that is already held in the LiveData is passed back again.
You can solve this in a couple of different ways: You can use a SingleLiveEvents to wrap your list and check if the data is used every time you receive new data inside your fragment. If it's not used means that is fresh new data coming from the ViewModel. I use something like this:
class SingleLiveData<T>(dataToBeConsumed: T? = null) {
private var _data: T? = dataToBeConsumed
val isConsumed
get() = _data == null
fun consumeData(): T {
val curData = _data!!
_data = null
return curData
}
fun consumeDataSafely(): T? {
val curData = _data
_data = null
return curData
}
}
This will cause to modify the ViewModel and have this instead:
val mutableLiveData = MutableLiveData<SingleLiveData<List<News>>>()
And change the way you populate the data like
viewModel.mutableLiveData.value = SingleLiveData(newsList)
In your code, you will check if the data isConsumed
before updating the RecyclerView.
//observe to the viewModel
viewModel.getNewsList(Timestamp.now()).observe(viewLifecycleOwner, Observer {
// Now it is SingleLiveData<List>
if (!it. isConsumed)
newsAdapter.submitList(it.consumeData())
})
You can browse more about the topic: https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70
Another approach is to use DiffUtil when updating your recyclerView this will cause only to update new objects and not have duplicates. Ref: https://blog.mindorks.com/the-powerful-tool-diff-util-in-recyclerview-android-tutorial
Unrelated to your problem, I advise not to hold ViewModel reference in your FirebaseRepo but return the data using a callback lambda function. You are creating a cyclic dependency which can cause bugs and problems in your app.
Upvotes: 2