Android - what is the meaning of StableIDs? - android

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().

Related

What does it mean to return different value for each position in RecyclerView adapter getItemViewType()?

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.

setHasStableIDs(true) in RecyclerView

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

How to use RecyclerView.Adapter notifyItemInserted/Removed in a right way?

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

Android AdapterView?

Documents say:
When the content for your layout is dynamic or not pre-determined, you
can use a layout that subclasses AdapterView to populate the layout
with views at runtime. A subclass of the AdapterView class uses an
Adapter to bind data to its layout.
But most of tutorials are in about ListView,GridView,Spinner and Gallery.
I'm looking to extend a subclass directly from AdapterView. I have to create a custom view that it's content is dependent to an adapter.
How can I do this, and what methods must be overridden?
First, you should be absolutely sure that AdapterView is what you want, because not all "dynamic or not pre-determined" views can be implement via AdapterView. Sometimes you'd better create your view extending ViewGroup.
if you want to use AdapterView, take a look at this really nice example. There are a lot of custom views with adapter on GitHub. Check out this one (extends ViewGroup).
This may not be a total answer to your question but i am showing you most probably a starting point or pointer which can guide you:
Sony Developer Tutorials - 3D ListView
ListView extends AbsListView which in turn extends AdapterView<ListAdapter>. So if you absolutely must implement such a custom view from scratch, you could have a look at the source code of those classes:
ListView
AbsListView
AdapterView
But beware, that's quite a task. Perhaps it may be sufficient to use one of the existing classes and tweak the look.
Deriving from AdapterView can work, but it may not be as beneficial as you hope. Some of the infrastructure provided by AdapterView is package-private, meaning we don't have access to it.
For example, AdapterView manages the selected item index for AbsListView and ListView. However, because methods like setNextSelectedPositionInt(int position) (which is the only path to setting mNextSelectedPosition) are package-private, we can't get to them. AbsListView and ListView can get to them because they're in the same package, but we can't.
(If you dig into the AdapterView source you'll find that setNextSelectedPositionInt() is called from handleDataChanged(). Unfortunately handleDataChanged() is also package-private and is _not_called from anywhere else within AdapterView that could be leveraged to enable setting position.)
That means that if you need to manage selected position, you'll need to recreate that infrastructure in your derived class (or you'll need to derive from ListView or AbsListView...though I suspect you'll run into similar problems deriving from AbsListView). It also means that any AdapterView functionality that revolves around item selection likely won't be fully operational.
You could create something like this :
public class SampleAdapter extends BaseAdapter {
public SampleAdapter() {
// Some constructor
}
public int getCount() {
return count; // Could also be a constant. This indicates the # of times the getView gets invoked.
}
public Object getItem(int position) {
return position; // Returns the position of the current item in the iteration
}
public long getItemId(int position) {
return GridView.INVALID_ROW_ID;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.some_layout, null);
view.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
view.setBackgroungColor(Color.RED);
return view;
}
}
And this could be invoked like :
GridView sampleView = (GridView) linearLayout.findViewById(R.id.sample_layout);
sampleView.setAdapter(new SampleAdapter());

Get Position from SimpleCursorAdapter

Is there a way to do this? My specific purpose is that I am trying to populate a label from an edittext within my list that is populated by a simplecursoradapter. When I gather the information from the cursor is only gives me the value from the most recent item in the list.
Example:
lbs = (TextView)view.findViewById(R.id.GrainLbs);
lbs.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent arg1) {
if(!popState){
parentActivity.showPopup(v, mView, getId, String.valueOf(getLbs));
Log.i("Current ID", "");
}
setPopState();
return true;
}
});
I'm trying to pass the value from the database to a method in my activity from within my custom simplecursoradapter (String.valueOf(getLbs)) which since i'm gathering this information by clicking on an object in the listview I thought it would automatically use the data from the specific list item. Is there a way I can get the list item position and then use info from the database based on the position?
If you are extending ListActivity, then you can override ListActivity.onListItemClick. The second argument passed into that function is the position in the list, which is the same as the position in your SimpleCursorAdapter.
This turned out to be such a simple answer and I can't believe i've spent so much time trying to figure it out. Because I was collecting the value within an inside method (an onclicklistener) I needed to make my variable final, it was declared at the beginning of the class so it could be used throughout, and because of this I wasn't getting an exception, but I needed to make it final before it would pass the correct value for whatever reason.
So I guess if you bind your views within a customcursoradapter you can collect values for each individual listitem without knowing the position, it automatically knows this for you.

Categories

Resources