Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PagedListAdapter.submitList() Behaving Weird When Updating Existing Items

Little story of this topic : the app just updating clicked row's values with dialog when confirmed. Uses pagination scenario on room database.

When an item added or removed, the latest dataset is fetched and passed to submitList method, then all changes are seen and worked well.

The problem starts there, if an existing item updated, again the latest dataset is fetched properly and passed to submitList, but this time changes didn't seem.

When i debug the DIFF_CALLBACK and caught my item in areItemsTheSame, the newHistory and oldHistory values are same! (How!)

There could be any bug in submitList method ?

  • Room v. : 2.1.0-alpha02
  • Paging v. : 2.1.0-beta01

After initializing, observe fetches list from room and passes to mHistoryAdapter.submitList(it). Then if i update an item, observe gets triggered again(and i'm seeing updated value in param it) and passes to submitList.

Unfortunately, adapter wont change...

    mResolvedAddressViewModel = ViewModelProviders.of(this).get(ResolvedAddressViewModel::class.java)
    mResolvedAddressViewModel.getAddresses(false).observe(this, Observer {
        mHistoryAdapter.submitList(it)
    })

All the parts

Model

@Parcelize
@Entity
data class ResolvedAddress(
    @PrimaryKey var id: String = UUID.randomUUID().toString(),
    var requestedLat: Double = 0.0,
    var requestedLon: Double = 0.0,
    var requestedAddress: String = "",
    var lat: Double,
    var lon: Double,
    var address: String,
    var country: String,
    var countryCode: String,
    var city: String,
    var alias: String? = null,
    var favorite: Boolean = false,
    var provider: String? = null,
    var lastUseDate: Long = 0L) : Parcelable

Adapter

class HistoryAdapter(var context: Context)
: PagedListAdapter<ResolvedAddress, HistoryItemHolder>(DIFF_CALLBACK) {

    companion object {
        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ResolvedAddress>() {
            override fun areItemsTheSame(
                oldHistory: ResolvedAddress, newHistory: ResolvedAddress): Boolean {
                return oldHistory.id == newHistory.id
            }

            override fun areContentsTheSame(
                oldHistory: ResolvedAddress, newHistory: ResolvedAddress): Boolean {
                return oldHistory == newHistory
            }
        }
    }
}

Fragment

class HistoryFragment : Fragment() {
    private lateinit var mHistoryAdapter: HistoryAdapter
    private lateinit var mResolvedAddressViewModel: ResolvedAddressViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 
        savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_history, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerViewHistory.setHasFixedSize(true)
        recyclerViewHistory.layoutManager = LinearLayoutManager(activity)
        recyclerViewHistory.itemAnimator = DefaultItemAnimator()

        mHistoryAdapter = HistoryAdapter(context!!)
        recyclerViewHistory.adapter = mHistoryAdapter

        mResolvedAddressViewModel = ViewModelProviders.of(this)
        .get(ResolvedAddressViewModel::class.java)

        mResolvedAddressViewModel.getAddresses(false).observe(this, Observer {
            mHistoryAdapter.submitList(it)
        })
    }
}
like image 870
blackkara Avatar asked Nov 21 '18 07:11

blackkara


3 Answers

There's a couple things missing from the question that could help provide a more detailed answer.

Ex. What does your RecyclerView.Adapter look like? Does it extend PagedListAdapter?

What does your model class look like? Is it a Kotlin data class?


For the sake of providing an answer, let's assume those unknowns are what we expect.

If I understand the question, it seems like you're just updating an item and not removing or adding any items. Therefore, the DiffUtil.ItemCallback's areItemsTheSame will always return true, because the old list and new list has not been modified in terms of their size. Meaning, if you've updated an item, you've probably updated it's contents and not removed it from the list.

Therefore, areItemsTheSame will return true, because their ids are still the same.

It's more likely that the second method, areContentsTheSame will return false since you've updated the item's content.

If your model class, ResolvedAddress, is a Kotlin data class, then the method areContentsTheSame should return false when comparing the item that was updated from the old list and the new list. This should trigger the onBindViewHolder method in your adapter at this point for you to rebind that item with the updated data.

If that model is not a Kotlin data class, than you must make sure the class implements the compareTo method. If not, you are comparing the object's memory address vs the actual contents of the object. If that is the case, the method areContentsTheSame will always return true, since the object's memory address has not changed.

These are some debugging tips, as it is difficult to provide a clearer answer without more knowledge about how the code has been implemented.

like image 117
Felipe Roriz Avatar answered Nov 06 '22 07:11

Felipe Roriz


I was having a similar issue but managed to fix it by updating the existing item with a new object rather than directly updating the existing item, as suggested by this answer here:

https://stackoverflow.com/a/54505078/10923311

like image 2
SQLol Avatar answered Nov 06 '22 07:11

SQLol


The issue is with how submitList processes changes. If you are passing a reference to the same list, it will not show updates as it determines it is the same datasource. In Kotlin if you want to update the sourceList and pass it back to submitList, you can do so as follows:

submitList(originalList.toList().toMutableList().let {
     it[index] = it[index].copy(property = newvalue) // To update a property on an item
     it.add(newItem) // To add a new item
     it.removeAt[index] // To remove an item
     // and so on....
     it
})
like image 1
UnOrthodox Avatar answered Nov 06 '22 07:11

UnOrthodox