Search code examples
androidkotlinandroid-listviewandroid-livedata

Update element in ListView from LiveData in ViewModel on Android using Kotlin


I am making an app that is composed of a single activity.

The user can create an array of predefined size by clicking a button. And with an other button, I want the array to be sorted using various algorithms.

The algorithm and their performance time are displayed in a ListView. See the screenshot below as an example: example of the activity at launch

My listView is created like this in MainActivity:

listView.adapter = MyCustomAdapter(this)

and below is the adpter code:

class MyCustomAdapter(context: Context): BaseAdapter() {

    private val mContext: Context = context

    val names = MainActivityViewModel().allAlgorithms

    override fun getCount(): Int {
        return names.size
    }

    override fun getItem(p0: Int): Any {
        return names[p0]
    }

    override fun getItemId(p0: Int): Long {
        return p0.toLong()
    }

    override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {

        val layoutInflater = LayoutInflater.from(mContext)
        val row = layoutInflater.inflate(R.layout.row_algorithm, p2, false)

        val nameTextView = row.findViewById<TextView>(R.id.algoName)
        nameTextView.text = names[p0].name

        val timer = row.findViewById<TextView>(R.id.timer)
        timer.text = names[p0].time

        return row
    }
}

allAlgorithms that comes from my ViewModel is a MutableList<Algorithm>.

The Algorithm class:

class Algorithm(var name: String, var time: String = "0.00 sec")

For the moment this MutableList<Algorithm> is initialized using a listOf(various algo names). This means that the time is kept to default value.

Now when the user clicks the start benchmark button, this function is called in my ViewModel:

private var _executionTime = MutableLiveData<Long>(timer)
val executionTime: LiveData<Long>
    get() = _executionTime

private var _index = MutableLiveData(0)
val index: LiveData<Int>
    get() = _index

fun startBench() {
    for ((i,v) in names.withIndex()) {
        _executionTime.value = measureTimeMillis {
            arr.sort() // is the arr generated by clicking the button
        }
        _index.value = i
    }
}

I will make different functions for each algorithms later, but for now I want my ListView to be updated to my executionTime.value() for the right algorithm.

I have tried to put an observer inside my adapter, but I cannot get a ViewModelProvider since I did not find a way to access a LifeCycleOwner.

I also have tried to update my allAlgorithm list but of course it doesn't update the UI.

MY QUESTIONS:

  • How can I update my ListView using LiveData ?
  • Is there a better way to achieve what I want to achieve ?

Solution

  • I was doing a pretty obvious mistake that was causing my notifyDataSetChanged() not to work:

    In MyCustomAdapter instead of passing the original version of the MainActivityViewModel, I was instanciating new viewModel every call which is not smart.

    The easy fix was to pass my instance of MainActivityViewModel to the adapter like this:

        val adapter = MyCustomAdapter(this, viewModel)
        listView.adapter = adapter
    

    And mofify the adapter accordingly:

    class MyCustomAdapter(context: Context, vm: MainActivityViewModel): BaseAdapter()
    

    and the changes were taken into account.