Menu
Who Do Is
  • Home
  • What
  • How
  • Is
  • Can
  • Are
  • Does
  • Do
  • Why
  • Who
  • Where
  • Which
  • Which
  • Should
  • Will
  • When
  • What’s
  • Did
Who Do Is

[ANSWERED] android – ViewModel is duplicating items for recyclerview

Posted on November 14, 2022

Solution 1 :

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.

Solution 2 :

The problem is in your destructuring:

READ  [ANSWERED] android - Change Dimension of nested layout in MotionLayout
Powered by Inline Related Posts

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..

Problem :

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)
}

}

Comments

Comment posted by nirazverma

can u write the full code, please because i am new in SingleLiveData and the documentation you shared is written in java language please..

READ  [ANSWERED] android - Why does my button run both the onTap function and the onLongPressDown function at the same time?
Powered by Inline Related Posts

Comment posted by Viktor Petrovski

The SingleLiveData implementation is enough to get you started. I’ve edited my answer to show an example of how to observe the data. Let me know if you need any other assistance.

Comment posted by nirazverma

onScroll Recylerviewonscroll listener val list = viewModel.mutableLiveData.value!!.consumeData() viewModel.getNewsList(list[list.size – 1].timestamp) i am getting error kotlin.KotlinNullPointerException

Comment posted by nirazverma

and also where to use consumeDataSafely() and isConsumed?

Comment posted by Viktor Petrovski

I just noticed that you are calling getNewList from inside onScroll. You should change this approach and access the data that is already in the RecyclerView. You can get the last timestamp using: val lastItem = newsAdapter.items[newsAdapter.items.size – 1] (Where items is the ArrayList inside your adapter) You can ignore consumeDataSafely() for now, that is just the null check if you are not sure if your data is null or not. isConsumed is used before calling the newsAdapter as shown in the example if (!it. isConsumed) newsAdapter.submitList(it.consumeData()) @nirazverma

Recent Posts

  • How can I play with my cat without toys?
  • What is a bag pipe band called?
  • Are Honda Civics actually fast?
  • Are Yankee candles toxic?
  • How do I pair my Michael Kors smartwatch with my Android?

Recent Comments

No comments to show.

Archives

  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022

Categories

  • ¿Cómo
  • ¿Cuál
  • ¿Cuántas
  • ¿Cuánto
  • ¿Que
  • ¿Quién
  • 90” and 108” so you may have to round up to the nearest size.
  • and delete any Spotify folders from it. Once this is done
  • Android
  • Are
  • At
  • Bei
  • blink
  • C'est
  • Can
  • carbs
  • Comment
  • Did
  • Do
  • Does
  • During
  • For
  • Has
  • How
  • In
  • Is
  • Ist
  • Kann
  • Können
  • nouveau
  • On
  • or 108 inches.2020-08-03
  • Où
  • owning
  • Pourquoi
  • Puis-je
  • Quand
  • Quante
  • Quel
  • Quelle
  • Quelles
  • Quels
  • Qui
  • Should
  • Sind
  • Sollte
  • spiritual
  • tap the downward-facing arrow on the top left. A downward-facing arrow will appear underneath each song in the album; they'll turn green as the download completes.2020-07-28
  • Uncategorized
  • Wann
  • Warum
  • Was
  • Welche
  • Welcher
  • Welches
  • Welke
  • Wer
  • Were
  • What
  • What's
  • When
  • Where
  • Which
  • Who
  • Whose
  • Why
  • Wie
  • Will
  • Wo
  • Woher
  • you will receive two curtains each with the same measurements of width 66"" (168cm) x drop 54""(137cm).
  • you'll see a green downward-facing arrow next to each song.2021-02-26
©2023 Who Do Is | Powered by SuperbThemes & WordPress