I am new to android and got stuck when I click on an item in RecyclerView where the data set gets changed and position doesn't match with the ID in SQLite.I know we can get unique ID by using 'setHasStableID' but I was little confused as to where do I need to set this 'setHasStableId(true)' condition? How does this work?
The setHasStableIds(true) is to be applied to the adapter of RecylerView.
adapter.setHasStableIds(true);
Also for this to be taken effect you must have to override getItemId(int position), to return identified long for the item at position. We need to make sure there is no different item data with the same returned id.
The id can be an id from the database which will be unique for each item and are not changed throughout.
//Inside the Adapter class
#Override
public long getItemId(int position) {
return itemList.get(position).getId();
}
This will reduce the blinking effect on dataset notify, where it modifies only items with changes.
And the great part is it will add cool animations on item position changes!.
To solve blinking issue, we need to reuse the same ViewHolder and view for the same item. because
RecycleView disabled stable IDs by default.
So generally after
notifyDataSetChanged(), RecyclerView.Adapter didn’t assigned the
same ViewHolder to original item in the data set.
So solution is :-
setHasStableIds(true) :-
set setHasStableIds(true); in RecyclerView.Adapter .
true means this adapter would publish a unique value as a key for
item in data set.
Adapter can use the key to indicate they are the
same one or not after notifying data changed.
override getItemId(int position) :-
Then we must override getItemId(int position),to return
identified long for the item at position
We need to make sure there
is no different item data with the same returned id.
For new readers:
Note that for the Paging 3.0 library stable IDs are unnecessary and therefore unsupported.
Reference: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#getitemid
Related
I can see setting adapter.
setHasStableIds(true);
will improved RecyclerView performance, since it is improving lots of performance, why it is not default enabled in RecyclerView, Is there any limitations while setting setHasStableIds(true)?
The reason why it is not the default is because the data backing the adapter might not have stable Id's and the RecyclerView does not know this.
Yes there is a limitation one using setHasStableIds(true) - your data has to have stable Id's
You either need override getItemId(int position) to return a unique Id that would represent each Data Item, simply return the HashCode if you cannot think of anything better.
Or
If using something like a CursorAdapter this automatic has stable Id's as it uses the database table _id column.
The reason is when we are going to just displaying the data in recycler-view that case no need to unique id of each item and no animation required. so it's default its false.
But when we are going to perform some action in the dataset and update the recycler-view that case its need unique id for particular item and refreshing animation (blinking) it need.so we set as true
I am working on an Android app which has a recycler view. I have items ordered randomly in the recycler view. I want to move the fifth item of the list to the first position. And move the item previously at the first position to second position. Can anybody help me with this please?
You can use Collections.swap()
Swaps the elements at the specified positions in the specified list. (If the specified positions are equal, invoking this method leaves the list unchanged.)
METHOD
public static void swap(List<?> list,
int i,
int j)
Parameters:
list - The list in which to swap elements.
i - the index of one element to be swapped.
j - the index of the other element to be swapped.
SAMPLE CODE
// first swap the item using Collections.swap() method
Collections.swap(yourList, firstPosition, positionToSwap);
// than notify your adapter about change in list using notifyItemMoved() method
YourAdapter.notifyItemMoved(firstPosition, positionToSwap);
The way RecyclerView works is every time a new item comes on screen it calls your custom RecyclerView.Adapter subclass. Adapter has a reference for the dataset you take data for views from and gets passed the ViewHolder (the layout of an item) and index into onBindViewHolder(MyViewHolder holder, int position) to take dataset[position] and put the data into holder.textview.setText(dataset[position]).
As such, to swap places of elements you have 2 options:
rearrange data in the initial dataset. Chances are it's an ArrayList<> of some kind and you do TEMP=dataset[5];ArrayList.remove(TEMP);ArrayList.insert(TEMP,1); ArrayList will shift everything as required.
if it is important to keep dataset intact, you can rewrite your adapter, so that it keeps a map of { position : dataset_index } and populates items according to that map. That should be trivial.
And then you have to refresh the data with adapter.notifyDataSetChaged();
As the title says, If I Override getItemViewType(int position) in the adapter of a RecyclerView:
#Override
public int getItemViewType(int position) {
return position;
}
Does that mean I won't have views recycled? Since it means I have a different type of view in each position?
If so, is this equivalent to setIsRecyclable(true) ?
I'm asking this because I faced a problem with RecyclerView items mixed and duplicated in endless scrolling. Tried getItemViewId() to return a unique Id but didn't work. The only two ways it worked is by setIsRecyclable(true) and returning different value for each position from getItemViewType(position)
getItemViewType() tells the RecyclerView what kind of view is at the position. See the documentation.
getItemViewType
Return the view type of the item at position for the purposes of view recycling.
The default implementation of this method returns 0, making the assumption of a single view type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.
...
Returns int integer value identifying the type of the view needed to represent the item at position. Type codes need not be contiguous.
Your code won't prevent views from being recycled. I do not think that you want to return the position since that indicates that you have a different type of view for every position.
For instance, you have an adapter and in onBindViewHolder method you set OnClickListener to some views (and do some actions there depending on view position). You should assign final to position param of method onBindViewHolder so it could be accessible from onClick().
After changing dataset (remove or add item in list) you call onItemInserted or onItemRemoved and this really adds/removes a view in the recyclerview, BUT it does not refresh other viewitems so when you click on a neighbor viewitem it will open a screen or show data with invalid index. To avoid this I basically call notifyDatasetChanged to call onBind to all visible views and remove/add some views.
So how to refresh other views when you call notifyItemInserted/removed or how to work with these methots appropriately?
Assigning the position to a variable in onBindViewHolder will lead to an inconsistent state if items in the dataset are inserted or deleted without calling notifyDataSetChanged.
To use onItemInserted or onItemRemoved the data in the viewholder should remain consistent since it will not be redrawn and onClick would use this invalid position assigned before an item was added or removed.
For this and other use cases the RecyclerView.ViewHolder provides methods to access its position and id:
Use getAdapterPosition() or getItemId() to get valid positions and ids.
Also have a look on the other methods available in RecyclerView.ViewHolder.
So, the way I fix the problem I had is by changing the position into viewHolder.getAdapterPosition()
Cheers!
I advise you to add notifyItemRangeChanged after insert or remove list inside adapter. This work for my project.
Example in remove item :
public void removeItem (int pos) {
simpanList.remove(pos);
notifyItemRemoved(pos);
notifyItemRangeChanged(pos, simpanList.size());//add here, this can refresh position cmiiw
}
For future readers, this is what I do when inserting/removing in recyclerview
For example, my model class is CarsModel
In my adapter
ArrayList<CarsModel> carsModel;
In onBindViewHolder
CardModel model = carsModel.get(position);
When removing data in list using button in holder:
int position = holder.getAdapterPosition();
carsModel.remove(position);
notifyItemRemoved(position);
Then when inserting
carsModel.add(0, model);
notifyItemInserted(0);
or insert in last row
carsModel.add(carsModel.size() - 1 , model);
notifyItemInserted(carsModel.size()-1);
I'm implementing a ListAdapter of ExpandableListView, while working i see that i need to overide the function boolean hasStableIds().
Can anyone explain please what is the meaning of stable ids? when I need this?
Stable IDs allow the ListView to optimize for the case when items remain the same between notifyDataSetChanged calls. The IDs it refers to are the ones returned from getItemId.
Without it, the ListView has to recreate all Views since it can't know if the item IDs are the same between data changes (e.g. if the ID is just the index in the data, it has to recreate everything). With it, it can refrain from recreating Views that kept their item IDs.
If hasStableIds() returns false then each time you call notifyDataSetChanged() your Adapter will look at the returned value of getItemId and will eventually call getView(int position, View convertView, ViewGroup parent). If hasStableIds() returns true the this will only be done when their id has changed.
Using this technique you can easily update only one Item in the ListView
If you implement getItemIdcorrectly then it might be very useful.
Example :
You have a list of albums :
class Album{
String coverUrl;
String title;
}
And you implement getItemId like this :
#Override
public long getItemId(int position){
Album album = mListOfAlbums.get(position);
return (album.coverUrl + album.title).hashCode();
}
Now your item id depends on the values of coverUrl and title fields and if you change them and call notifyDataSetChanged() on your adapter, then the adapter will call getItemId() method of each element and update only those items which id has changed.
This is very useful if are doing some "heavy" operations in your getView().