1

I've been stuck on this for a couple of days and could really use some help and understanding on how to setup RecylerView(RV) with DiffUtil, seems whatever I try I can't get the RV to update. I'll run through what I have and hopefully, someone can shed some light on what I'm doing wrong.

App start

  • Create ViewModel,
    • The LiveDataList is then populated in the ViewModel.
  • The RV is instantiated
    • The LayoutManager and the ListAdapter are applied to the RV.
  • ItemTouchHelper is attached to the RecyclerView allowing items to be deleted from the LiveDataList when swiped to the left.
  • The LiveDataList is observed, My understanding is when a change happens to the list, the observer will submit the List to the AdapterList to compare it with the old list if there is a difference between the original submitted listed and the modified list the AdapterList will update the RecyclerView.

Why is the observer not being called to update when an item is added or removed from the list LiveDataList.

class MainActivity : AppCompatActivity() {
    var myAdapter = RVAdapter()
    private lateinit var viewModel: MyViewModel()

    override fun onCreateView(savedInstanceState: Bundle? ) {
        super.onCreate(savedInstanceState)
        // Initialize the ViewModel, that stores the userList. 
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        // Creates list of users
        addUser()

        // Link an RV object to view
        val rv = findViewById<RecyclerView>(R.id.recycler_view)
        // apply RV Settings. 
        rv.apply {
            layoutManager = LinearLayoutManager(baseContext, LinearLayoutManager.VERTICAL, false)
            adapter= myAdapter
        }

        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
             override fun onMove(...) {
                 // move items isn't used. 
             }

             override fun onSwipe(viewHolder: ViewHolder, direction: Int) {
                 // When I swipe a second item, the app crashes.
                 val editList: MutableList<User>? = viewModel.usersList?.value as MutableList<User>?
                 editList?.removeAt(viewHolder.adapterPosition)
                 viewModel.usersList?.postValue(userList)
             }
        }).attachtoRecyclerView(rv)
     
        // The observer will run after swiping an item but the UI doesn't update. 
        viewModel.usersList?observe(this, Observer {
            it?.let {
                // Should update list but it doesn't
                myAdapter.submitList(it)
            }
    }

    private fun addUser() {
        // Shouldn't there be away to add Items directly to list in ViewModel.
        val newList: MutableList<User> = mutableListOf()
        newList.add(User("Shawn", 1)
        newList.add(User("Shannon", 2)
        newList.add(User("Don", 3)

        viewModel.userList?.postValue(newList)
}

Data class: This is pretty basic and not much happening here.

data class User(val name: String, val accountNumber: Int) {
}

ViewModel: MyViewModel only stores the MutableLiveDat

class MyViewModel : ViewModel() {
    val usersList: MutableLiveData<List<User>>? = MutableLiveData() 
}

RecycleView Adapter

class RVAdapter : ListAdapter<User, RVAdapter.ViewHolder>(MyDiffCallback() {
    
    // This DiffUtil class never gets called not even on startup.
    class MyDiffCallback: DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.name == newItem.name
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }

    }

    ... Rest of RV is working fine but can be included if it's helpful.  
}

What can be done so when an item is deleted or added from the LiveData List that the observer will pass the modified list to the ListAdapter?

StackTrace included Index outbounds.

2020-10-05 20:14:16.538 19042-19042/com.example.practicerecyclerview2 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.practicerecyclerview2, PID: 19042
    java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
        at java.util.ArrayList.remove(ArrayList.java:503)
        at com.example.practicerecyclerview2.MainActivity$onCreate$2.onSwiped(MainActivity.kt:100)
        at androidx.recyclerview.widget.ItemTouchHelper$4.run(ItemTouchHelper.java:712)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Shawn
  • 1,222
  • 1
  • 18
  • 41

1 Answers1

1

The reason your ListAdapter is not updating is that it cannot find a difference in the list you submit, because you already modified it. This way the notifyDataChanged() methods are not called and you get the IndexOutOfBoundsException on a second swipe, because that item does not exist anymore.

The way to solve it is to modify and submit a copy of the list:

override fun onSwipe(viewHolder: ViewHolder, direction: Int) {
    val editList: MutableList<User>? = viewModel.usersList?.value as MutableList<User>?
    val editListCopy = editList?.toMutableList()
    editListCopy?.removeAt(viewHolder.adapterPosition)
    viewModel.users?.postValue(editListCopy)
}
Bram Stoker
  • 1,202
  • 11
  • 14
  • I'm having a hard time wrapping my head around why the ListAdapter doesn't find a difference in the list if you add or remove an item from the list. Are you saying the AdapterList doesn't check modifications to a List but needs a new list object? – Shawn Oct 06 '20 at 20:52
  • 1
    Yes, it holds a reference to the same list you are modifying. Internally it does an equality check on the submitted list and its current list, but they are the same! – Bram Stoker Oct 07 '20 at 08:09
  • 1
    Also note that `ListAdapter` holds a reference to items in the list too. So if you modify an item it doesn't see a change, because we make a 'shallow copy' (new list, but the same items). To make `ListAdapter` see changes to items we have to make a 'deep copy' (new list and new items). The copy-function on data classes is shallow, so you have to do your own deep copy. – Bram Stoker Oct 07 '20 at 08:13
  • Thanks, @BramStoker this makes so much sense, – Shawn Oct 08 '20 at 06:20
  • @Bram Stoker I have a similar use case. Can you provide Java example of deep copy? – AJW Nov 11 '21 at 00:34