Save position of specific item in RecyclerView after new data insertion - android

I implement RecyclerView which have two ViewType. The list is dynamic and when user scroll down/up it add some items. In my RecyclerView just one of the item has different ViewType (consider this as expandable list which only one of item expand at a time).
I save position for expanded item But when new data added this position changed and I lost expanded item. Because data added in scroll down/up, updating expanded item according to size of new data is not good Idea.
One thing to mention is that I want to scroll to expanded item at first load. so I guess saving position would be best choice. (I guess but I'm not sure);
I want to know what's the efficient way to handle this issue?

http://tutorials.jenkov.com/java-collections/hashcode-equals.html
Implement hashcode and equals method, using this get the position for the expanded model object.
Example :
public class Employee {
protected long employeeId;
protected String firstName;
protected String lastName;
public boolean equals(Object o){
if(o == null) return false;
if(!(o instanceof) Employee) return false;
Employee other = (Employee) o;
if(this.employeeId != other.employeeId) return false;
if(! this.firstName.equals(other.firstName)) return false;
if(! this.lastName.equals(other.lastName)) return false;
return true;
}
public int hashCode(){
return (int) employeeId;
}
}
// To get the index of expanded item
int expandedItemIndex = EmployeeList.indexOf(expandedEmployeeModel);
recyclerView.scrollToPosition(expandedItemIndex);

Likely you use model for loading data to RecyclerView. And this model (for ex. ContactModel) contains different values for your ViewHolders.
What is point, that you use special references for that saved position. What you need to do, it's just put that position (of expanded item) to current model. And after all most works fine.

Related

How to update previous Items in Android Recycler view?

I have a RecyclerView to list a set of data. And on clicking each item , I have validation to check previous item is entered or not. If that item is not entered I want to enable an inline error (which is hidden in normal case) message in the previous row. I have done the scenario as shown below but error is showing only in the current row. Anyone suggest how I can enable/update previous row or a specific row.
public boolean _validateListItems(int itemIndex)
{
int previousItemIndex = itemIndex - 1;
for (int i = 0; i <= previousItemIndex; i++)
{
if ((listRecyclerItem.get(i).getEnable()==0))
{
return false;
}
}
return true;
}
holder.expand_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position>0){
if(_validateListItems(position))
{
mExpandedPosition = isExpanded ? -1:position;
notifyItemChanged(previousExpandedPosition);
notifyItemChanged(position);
notifyDataSetChanged();
}
else
{
holder.error.setVisibility(View.VISIBLE);
holder.error.setTextColor(ContextCompat.getColor(context, R.color.error_red));
}
}
}
});
First of all RecyclerView is something that recycle view. View is generated is based on Data Model.
So, lets store the user button/checkbox click/checked action) to the respective Model/Item. To run the validation, get the items from the Adapter and check your conditions in Activity/Fragment [Looping is an expensive operation, use Coroutine or RxJava]. Execute your validation and if Validation is true for an item, just update the Item from the list and finally update the Adapter. You can pass the Error message in the item and render it to the View. And finally, must use DiffUtil to update the items in adapter.
Declare an interface for your adapter that has callback to your viewModel and when each item changed you can return the call back ito viewmodel and store it in an object or array
for example :
interface Callback { void onDataChanged(int itemPosition); }
call that method in onBindViewHolder when your item text changed
and in view model add the returned item into a list
when you clicked on a button, you can check the items if your necessary item didn't exist you can return error

Android Recylerview doesn't render an item when it is moved to the end of the recyclerview whilst the end is onscreen

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));
}
}

Want Scrolling list to specific position

Can We scroll listview on basis of item name on button clicking in android.
Like I have list with multiple header and i want to scroll the list to that header on button clicking.
You can get list position by name using below function.
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof YourModel)) return false;
YourModel that = (YourModel) o;
return Objects.equals(itemName, that.itemName);
}
After that you can scroll to particular position using scrollToPosition(position);
For scroll to any postition X:
getListView().setSelection(X);
Smoothly scroll to the specified adapter position.
smoothScrollToPosition(int position)
if you are using Recyclerview then try this
yourRecyclerview.scrollToPosition(position);
And if you are using ListView then try this
// For smooth scroll:
getListView().smoothScrollToPosition(position);
//For direct scroll
getListView().setSelection(position);

Hide specific items (set in SharedPreferences) in RecyclerView

I want to filter/hide specific Items in the RecycleView if the item content matches with an preference set in SharedPreferences.
I guess I have to somehow prevent from these specific items from getting inflated in the adapter but I have no idea how.
Any ideas?
Cheers!
An adapter is the Model part of the Model-View-Controller design pattern for using ListView, GridView, and RecyclerView. So you have to think of it this way: The adapter, at any moment, has to reflect what you want to have displayed in the RecyclerView.
So here's an example: Let's say you have four items, and you want to filter the third item because it matches your preference. Your adapter's getCount() method must return 3. For getView(), position == 0 must return the first item view, position == 1 must return your second item view, and position == 2 must return your fourth item view.
It's up to your adapter code to figure out all calculations and offsets to make sure that it is always presenting a consistent state to the view. So for example, let's say you have a String array with the items, and an index dontshow pointing to the array item that shouldn't be displayed. You need to do something like this for getView():
int index = position; // position is input parameter
if (index >= dontshow) {
index++; // skip over the don't-show item
}
String item = items[index];
// now construct your view from this item
And
#Override
public int getCount() {
return items.length - 1;
}
Then when you make changes to your model, calling notifyDataSetChanged() tells the RecyclerView it has to call getCount() and getView() on your adapter all over again to redisplay the changed data.

RecyclerView.Adapter notifyItemChanged() with async network update?

I have a RecyclerView using a LinearLayoutManager, and a custom RecyclerView.Adapter. When a user long-clicks an item, it triggers an asynchronous network refresh of only that item. I know the item's position at the time of the long-click, and I can pass that position on to the network refreshing function. However by the time the refresh is complete and notifyItemChanged() is called, the user may have added a new item or removed one. So while the refreshed item may have originated from position 4, by the time the refresh is done it could be in 3 or 5 or somewhere else.
How can I ensure that I call notifyItemChanged() with the right position parameter?
Here are three possible solutions:
Call notifyDataSetChanged() instead and call it a day.
Keep a separate map of items by a unique ID in your adapter. Have the network refresh return item along with the unique ID. Access the item through the ID map and figure out its position. Obviously if there is no unique ID for your items, this isn't an option.
Keep track of the item(s) being refreshed. Register your own AdapterDataObserver and track all the inserts and updates, calculating the new position of the item each time and saving it until refresh returns.
While notifyDataSetChanged() will do the trick, if it is essential to know the position of the item, you can always go with implementation of hashCode and equals in the model class of the list item used in recyclerview adapter.
Implement hashcode and equals method to get the position for the required model object.
Example :
public class Employee {
protected long employeeId;
protected String firstName;
protected String lastName;
public boolean equals(Object o){
if(o == null) return false;
if(!(o instanceof) Employee) return false;
Employee other = (Employee) o;
if(this.employeeId != other.employeeId) return false;
if(! this.firstName.equals(other.firstName)) return false;
if(! this.lastName.equals(other.lastName)) return false;
return true;
}
public int hashCode(){
return (int) employeeId;
}
}
// To get the index of selected item which triggered async task :
int itemIndex = EmployeeList.indexOf(selectedEmployeeModel);
recyclerView.scrollToPosition(itemIndex);

Categories

Resources