0

I want to implement swipe to delete functionality in a RecyclerView which is populated with data from an API. There is a ViewModel class and data is fetched from API to a allOrders: MutableLiveData<MutableList<ModelClass>> in ViewModel. The observer in Fragment updates the RecyclerView Adapter on LiveData change:

viewModel.allOrders.observe(viewLifecycleOwner, Observer {
    adapter.submitList(it)
})

I want to delete the swiped item in RecyclerView from allOrders: MutableLiveData<MutableList<ModelClass>>, so this is the function to do that in ViewModel class:

fun deleteItem(index: Int) {
    _allOrders.value?.removeAt(index)
}

But removing an item from MutableLiveData<MutableList> doesn't even notify the observer.

What is the best practice for implementing such a functionality using ViewModel and LiveData in Kotlin?

UPDATE

I change deletItem function to this:

fun deleteItem(index: Int) {
    _allOrders.value = _allOrders.value?.also { list ->
    list.removeAt(index)
}

Now the observer is being notified, but adapter.submitList(it) doesn't change RecyclerView to new list. Scrolling the RecyclerView throw IndexOutOfBoundsException.

The adapter class looks like this one in codeslab example

AminA2
  • 188
  • 1
  • 12

3 Answers3

2

I had the same problem, submitList() was not updating the view. Found the solution here

Basically, the adapter class stores a reference to current list (call it curList). When submitList is called with a newList, it compares curList and newList, if they reference the same list, it silently ignores updating the view.

In the above solutions we are removing element in the list, so the reference is not changing. So when we call submitList with this new list, the function sees it as the same list, so it ignores updating view.

One solution is:

adapter.submitList(null);
adapter.submitList(it);

But this will call OnBindView on all list elements again, ignoring the DiffUtilCallBack.

Another is to create a shallow copy:

adapter.submitList(mutableListOf(it))

Make sure you call submitList this way at all places where the list changes.

or make allOrders: MutableLiveData<List<ModelClass>> and:

fun deleteItem(index: Int) {
    // minus() creates a new list automatically
    _allOrders.value = _allOrders.value?.minus(_allOrders.value?.get(index))
}

In this way, you can just call submitList(it).

1

You can have a 'reference list' in your ViewModel and operate on it. When the item is removed, then update the list and update the LiveData with this list:

class YourViewModel: ViewModel() {

    private var ordersList = MutableList<ModelClass>()
    val _allOrders = MutableLiveData<MutableList<ModelClass>>()
    
    fun deleteItem(index: Int) {
        ordersList.removeAt(index)
        _allOrders.value = ordersList
    }
}

You can also use Kotlin scope function called also without adding the globally scoped list if you don't need it and operate only on values emitted by LiveData:

fun deleteItem(index: Int) {
    _allOrders.value = _allOrders.value?.also { list -> 
       list.removeAt(index)
   }
}
Mariusz Brona
  • 1,549
  • 10
  • 12
  • I used `also` and it notifies observer, but calling `submitList(newList)` on adapter don't delete item in RecyclerView. Scrolling the RecyclerView throws `IndexOutOfBoundsException` . why is that? – AminA2 Oct 14 '20 at 09:33
  • Can you paste the Adapter class? – Mariusz Brona Oct 14 '20 at 09:40
  • I implemented adapter like this one in [codelabs examples](https://github.com/google-developer-training/android-kotlin-fundamentals-apps/blob/master/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepNightAdapter.kt) – AminA2 Oct 14 '20 at 09:47
1

The way you've implemented deleteItem(), circumvents the LiveData class. You're getting a reference to the List that is stored as it's value, and directly manipulating it, LiveData is unaware that anything has happened to the data.

Since Kotlin is a pass-by-reference language, the value that LiveData is holding is just a reference to the memory where the array is held, which means that if you change the contents of the array, the LiveData.value has not actually changed in any way.

You'll have to set a new value on the LiveData in order to update it and notify observers, in the case of a List, you're either gonna have to create a whole new List and set it as the value, or do this:

fun deleteItem(index: Int) {
    _allOrders.value?.removeAt(index)
    _allOrders.value = _allOrders.value
}
zOqvxf
  • 1,469
  • 15
  • 18
  • Problem solved. LiveData notifies observer, but calling `submitList(newList)` on adapter don't delete item in RecyclerView. Scrolling the RecyclerView throws `IndexOutOfBoundsException` . My RecyclerView adapter looks like [this one](https://github.com/google-developer-training/android-kotlin-fundamentals-apps/blob/master/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepNightAdapter.kt) – AminA2 Oct 14 '20 at 09:58
  • Are you using the `also {}` solution from the other answer? I think that might actually be breaking it. Try doing it how I said here. I think also gets called after the assignment, which would make it pointless. – zOqvxf Oct 14 '20 at 10:26
  • I tried your solution too. still throwing `IndexOutOfBoundsException` – AminA2 Oct 14 '20 at 12:29
  • sure. it's available in Codeslab example link – AminA2 Oct 14 '20 at 13:25