In my app I display a list of outfits in a 2 column GridLayout RecyclerView
, and allow users to swipe an outfit to the side. Upon swiping, I update the viewIndex of the outfit in the database (an integer which it uses for sorting the result of the "get all outfits" query), which causes the query my LiveData
(generated by Room
) is watching to change and put that item at the end of the returned list. This in turn calls a setList
method in my RecyclerViewAdapter
which uses DiffUtil
to update the list.
Everything works as expected in most cases. An item is swiped to the side, disappears, and if you scroll to the bottom of the RecyclerView
you can find it again at the end.
However, when the position in the RecyclerView
where this swiped item should appear (i.e. the bottom) is currently visible to the user, the item does not appear. If additional items are swiped while the end is still visible, they won't appear either.
Upon scrolling up and then back down, the items will now be in their proper places - it's fixed. I do not know why they are not rendered intially though - is this something to do with DiffUtil
perhaps? It could also have to do with my solution to this bug, where I save and restore the state of the RecyclerView either side of the setList call to prevent it scrolling to the new location when the first item of the list is moved (see BrowseFragment
below). I admit, I do not know exactly what that code does, I only know it fixed that problem. I tried commenting out those lines but it didn't affect the disappearing views.
How can I ensure the swiped items display immediately without requiring a scroll up? Below is a gif demonstrating the feature in use and then showing the problem (sorry for low quality, had to fit under 2MB).
Code in BrowseFragment.java where RecyclerView and Adapter are initialised:
RecyclerView outfitRecyclerView = binding.recyclerviewOutfits;
outfitsAdapter = new OutfitsAdapter(this, getViewLifecycleOwner(), this);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(requireActivity(), GRID_ROW_SIZE);
outfitRecyclerView.setLayoutManager(layoutManager);
outfitRecyclerView.setAdapter(outfitsAdapter);
//observe all outfits
outfitViewModel.getAllOutfits().observe(getViewLifecycleOwner(), (list) -> {
//save the state to prevent the bug where moving the first item of the list scrolls you to its new position
Parcelable recyclerViewState = outfitRecyclerView.getLayoutManager().onSaveInstanceState();
//set the list to the adapter
outfitsAdapter.setList(list);
outfitRecyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
});
Room
dao query used to generate the LiveData observed in outfitsViewModel.getAllOutfits()
@Query("SELECT * FROM outfits ORDER BY view_queue_index ASC")
LiveData<List<Outfit>> getAll();
setList
method in OutfitsAdapter.java, where outfits
is a private member variable containing the current list of outfits.
...
public void setList(List<Outfit> newList){
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new OutfitDiff(newList, outfits));
diffResult.dispatchUpdatesTo(this);
outfits = newList;
outfitsFull = new ArrayList<>(newList);
}
private class OutfitDiff extends DiffUtil.Callback {
List<Outfit> newList;
List<Outfit> oldList;
public OutfitDiff(List<Outfit> newList, List<Outfit> oldList) {
this.newList = newList;
this.oldList = oldList;
}
@Override
public int getOldListSize() {
if(oldList == null){
return 0;
}
return oldList.size();
}
@Override
public int getNewListSize() {
if(newList == null){
return 0;
}
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
}