0

I need to keep updating data in Viewpager2 i do it like this, call every five seconds

adapter?.updateData(it)      

fun updateData(items: List<TestDataObject>) {
        list = items
        notifyDataSetChanged()
    }

the problem is that the pages display the wrong text after notifyDataSetChange I think the problem is in notifyDataSetChange, but I don't know how to fix it.

MainActivity

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.view.View
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.lifecycleScope
    import by.lwo.viewpagertest.databinding.ActivityMainBinding
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    
    class MainActivity : AppCompatActivity() {
    
        lateinit var binding: ActivityMainBinding
        private var adapter: ViewPagerAdapter? = null
        val testList: MutableList<TestDataObject> = emptyList<TestDataObject>().toMutableList()
    
        private val _testLiveData = MutableLiveData<Event<List<TestDataObject>>>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
            setDataToList()
            setAdapter()
            constantlyUpdateList()
    
            _testLiveData.observeEvent(this) {
                adapter?.updateData(it)
                constantlyUpdateList()
           }
    
        }
    
        private fun constantlyUpdateList() {
            lifecycleScope.launch {
                delay(5000)
                setDataToList()
                _testLiveData.value = Event(testList)
            }
        }
    
        private fun setDataToList() {
            testList.clear()
            repeat(6) {
                testList.add(
                    TestDataObject(
                        titleText = "title for page $it",
                        infoText = "infoText for page $it"
                    )
                )
            }
        }
    
        private fun setAdapter(){
            binding.apply {
                adapter = ViewPagerAdapter(testList)
                viewPager.adapter = adapter
                viewPager.offscreenPageLimit = testList.size
            }
        }
    }

Adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import by.lwo.viewpagertest.databinding.HolderTestBinding

class ViewPagerAdapter(var list: List<TestDataObject>) : RecyclerView.Adapter<ViewPagerAdapter.TestHolder>() {

    lateinit var binding: HolderTestBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerAdapter.TestHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.holder_test, parent, false)
        binding = DataBindingUtil.bind(view)!!
        return TestHolder(view)
    }

    override fun onBindViewHolder(holder: ViewPagerAdapter.TestHolder, position: Int) {
        holder.bind(list[position])
    }

    override fun getItemCount(): Int = list.size

    fun updateData(items: List<TestDataObject>) {
        list = items
        notifyDataSetChanged()
    }

    inner class TestHolder(containerView: View) : RecyclerView.ViewHolder(containerView) {
        fun bind(testDataObject: TestDataObject) {
            binding.apply {
                titleTv.text = testDataObject.titleText
                textTv.text = testDataObject.infoText
            }
        }
    }

}

data class TestDataObject(val titleText: String = "", val infoText: String = "")

1 Answers1

1

I see two main problems:

  1. The notifyDataSetChanged method will force you adapter to re-render the whole set of items you have on your activity. It doesn't matter that your list differs from the new one in just one element: it'll get re-rendered in its totality.
    I'd recommend to use ListAdapter, which will handle you set for you with the submitList method. It is optimized for the use cases where you'll be updating your list very often.

  2. And most importantly, the way your adapter is built. You have the property:

lateinit var binding: HolderTestBinding

Which is being set in every onCreateViewHolder call. However, it is a class field, and every time it is set, the reference of the older holder will be lost and, in every TestHolder instance, you'll be referencing the last inflated binding, hence, all of your view holders will be referencing the exact same view holder, instead of their own.
A better way of doing this is passing the binding to the constructor of every holder:

class ViewPagerAdapter(...) : ListAdapter(...) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerAdapter.TestHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.holder_test, parent, false)
        val holder = DataBindingUtil.bind(view)?.let {
            TestHolder(it)
        }
        return holder!!
    }

    // 'inner' modifier is not needed
    class TestHolder(private val binding: TestHolderBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(testDataObject: TestDataObject) {
            // this 'binding' will reference their own binding, and not the 'global' one :)
            binding.apply {
                titleTv.text = testDataObject.titleText
                textTv.text = testDataObject.infoText
            }
        }
    }
}