Why does RecyclerView getAdapterPosition() return -1? - android

I am currently learning android and first time adding swipe to delete functionality to the RecyclerView. I came across the following theory regarding to the swiping functionality but i am unable to understand the reason discussed below as to why getAdapterPosition will return RecyclerView.NO_POSITION.
When adapter contents change (and you call notify***) RecyclerView requests a new layout.
From that moment, until layout system decides to calculate a new layout (<16 ms), the layout
position and adapter position may not match because layout has not reflected adapter changes yet.
If you are calling notifyDataSetChanged, it invalidates everything, RecyclerView does not know
that ViewHolder's adapter position until next layout is calculated.
In that case, getAdapterPosition will return RecyclerView.NO_POSITION (-1)
If you've called notifyItemInserted(0), the getAdapterPosition of ViewHolder which was
previously at position 0 will start returning 1 immediately.
As long as you are dispatching granular notify events, you are always in good state (we know
adapter position even though new layout is not calculated yet).
Can someone please explain the above reason and the solution in a simple way.

-1 means the scroll could not be computed or the actual position is not valid (in between).
The solution is ignore it
if (getAdapterPosition() == -1) return;

Related

RecyclerView NotifyItemInserted

I have a RecyclerView.Adapter with hasStableIds(true); and a LinearLayoutManager with reverseLayout(true);
I insert a new item to the object list in the adapter then call NotifyItemInserted(0). The list is reversed and loads from bottom to top with the new item always at the bottom.
The problem is when calling NotifyItemInserted all of the ViewHolders are "Refreshed" / "Reloaded" / "Recreated" - The viewholders are a bit complex and do not want them all to be recreated each time.
When I debug the solution, OnBindViewHolder starts from position 0 (Which is correct) then goes up to position 10 or 11 depending on how many items there are on screen, recreating them all.
I have tried many different settings on the adapter, layoutmanager and the recyclerview itself which none of them work.
I see there is a payload which can be passed in NotifyItemChanged but not in NotifyItemInserted. Maybe with the payload I can check whether the item is already on screen and don't recreate it again, I don't know what to do.
I am using Xamarin.Android but that should not be a problem here.
Removing hasStableIds(true) should solve your problem.
Reason:
When you set hasStableIds to true, the Adapter assumes that every item has a unique id, and it uses the values returned from the getItemId(int position) method to animate data changes for you.
That's an easy way to animate changes without any effort (As long as the getItemId(int position) really returns a unique id per item, otherwise you'll crash).
But in your case you want to animate changes by yourself and control which items will be re-rendered and which will not.

RecyclerView onBindViewHolder called multiple times for one item

I have used the RececlerView with the ViewHolder pattern for a while now.
Im am implementing a custom Adapter.
Im am not searching for a specific bug help in my code.
I was just wondering, if it's normal, that the onBindViewHolder method is called multiple times (for the same item) while scrolling to the end of the list and scrolling back up. In this case onBindViewHolder is called again for item 0 and 1 (the list contains 7 items in total)
Is there any possibility for this method to get called AGAIN without notifying that the datasat has changed?
Im a bit confused.
Kind Regards,
Palm
Yes it is perfectly normal for a RecyclerView to call onBindViewHolder() multiple times.
A RecyclerView only creates minimum number of Views needed to fill the screen. And it works by reusing the old/created Views. So that when you are scrolling down the View that hid during the scrolling to the top is removed and brought next to the last visible View and added there. But since the View is currently bound with old data onBindViewHolder() is called again to ensure that the View is bound with only the correct data before it is rendered.
Similarly you'll notice that onCreateViewHolder() is only called the exact minimum number of Views it needs.
For a better understanding of how the RecyclerView works I suggest you read up on Recycler, LayoutManager and Recycler.Adapter the three core parts of a RecyclerView.

RecyclerView: Race condition between notify* and scrollToPostion?

Is is possible to have race conditions between the notify* methods of a RecyclerView.Adapter and scrollToPosition (and smoothScrollToPosition) of the RecyclerView itself? If so, how can I force the scroll to happen strictly after the notify has been applied?
In a bit more detail: I have a RecyclerView with an adapter that frequently is updated with new items (which may or may not overlap with the previous items). Also, whenever I set new items I also want to set the scroll position to a specific item. To that end, I first update the items inside my Adapter and then scroll the RecyclerView.
However, more often than not the scroll position will be wrong after this process. Also, if I then issue another smoothScrollToPosition command without changing the data, the scrolling is weird: It sometimes goes in the wrong direction, etc. After this second scrolling, the position is always correct however. So, it seems that something goes wrong the first time and the RecyclerView catches and corrects that error on the second scroll.
Also, the errors are slightly different when I use notifyDataSetChanged from when I use DiffUtil.
Now I've read in this response by Yigit that notify* is basically asynchronous, so I suppose there can be a race condition between them and the subseqent scrollToPosition - is that correct?
Finally what can I do to establish a strict ordering, so that the scroll is only called when all ViewHolder updates triggered by notify are done?

How to remember item "physical" position in recyclerView?

I have a dialog with RecyclerView of IDs in it. While I was choosing an id it changes its background. I did it by replacing adapter where I am putting newly selected position. And in my adapter item layout was chosen by its position where if equals to selected, it has another layout.
So after resetting a new adapter to recycler it losses current scroll position. I tried to use scrscrollToPosition, but it still not perfect, due to the situation that scrolled position is always on a beginning of RecyclerView.
So as I understand the problem I need to remember reference or some other scrolling data for setting it on the new adapter.
So If someone knew relevant RecyclerView methods or good practice for this problem, it would be nice =)
I don't think that here I need some code, cause it more practice question, but if someone need it I would add it.
Update: Meantime I found this method for getting offset of RecyclerView but still looking how to use this data in new adapter.
int offset = recyclerViewDialog.computeHorizontalScrollOffset();

Dynamically adding Views in a RecyclerView only to current item

I'm dynamically adding Views to my items in a RecyclerView. These added Views should only be related to the item which they're added to, but I'm having a problem when I scroll. It seems the View is recycled and a new item is loaded, but those previously added views are still there, only now on the wrong item.
I'm assuming that it's just because the ViewHolder is being reused, so the added items show up again with a new item, when loaded.
How would one go about solving this?
This was an old question of mine. A bounty was placed on it, hence the surge of popularity and the multiple new and irrelevant answers.
As stated in both my comment to this answer and #CQM's comment below my original question, the answer is to override the onViewRecycled() method and perform any needed operations there. This method is called when a view is recycled, and any cleanup operations can be done here.
Documentation on this method can be found here.
In my case, it was a matter of deleting the invisible TextView's attached to the view. The text itself had been deleted, but the view remained. If many invisible TextView's accumulate on the view and aren't properly recycled when scrolling, the scroll will begin to lag.
You need to track what views have been added based on the backing data. I would probably add any necessary extra views in onBindViewHolder(), and remove any that might be present in onViewRecycled(). Then when you want to make one appear dynamically, change whatever variable you have tracking whether it should be visible, and call notifyItemChanged().
Based on this:
but those previously added Views are still there, but now on the wrong item.
Basically, as per the RecyclerView documentation, You have to reset the views everytime inside the onBindViewHolder() method,
so let say, you have a method that sets a view param if its your profile, so the code for the same goes as follows,
if (list.get(position).getId()==PreferenceManager.getUserID())
{
// do some view change here
setViewParam(true);
}else
{
// reset the view change here
setViewParam(false);
}
So what you're doing here is giving recycled ViewHolder a chance to reset.
Do comment if you need help!
You can use this! setItemViewCacheSize(int size)
Check here RecyclerViewDocumentation.
The offscreen view cache stays aware of changes in the attached adapter, allowing a LayoutManager to reuse those views unmodified without needing to return to the adapter to rebind them.
First of all, can you share some more code please?
Second, why would you want to dynamically add new views on fly? Why don't you use different VIEWTYPE or just have those view already on your layout and just make them visible/invisible or visible/gone? (I believe it will be more efficient this way).
Let me remind you something about RecyclerView, yes when user is scrolling viewHolder are being reused (few of them can be created, even more than it needs to fill the screen). So if it happened that you added some views on "item A" and user scroll to "item Z", that viewHolder can be reused for that "item Z", hence the show up of the previously added views.
How can you solve that?
Well always check on every items if you need to add new views, if yes add them if not already added, else always remove those views (if present) to return to default viewHolder state (or whatever you call it).
Hope this will help you.
Save Information by tags for items with new child each time the Add newView operation occur. (In shared preference for example)
Tag: create with item position onBindViewHolder.
...
SharedPreference sharedPref = getSharedPreference("text" + position, context);
SharedPreference.Editor editor = sharedPref.edit();
editor.putString("view", "ImageView");
...
when load Adapter get this value and put default as null.
I am not sure about its efficiency but i will work.
...
String viewType = sharedPref.getString("view", null);
//it will return ImageView
if you know some possible viewTypes for example always going to be ImageView & TextView so with some if statement it will be ok.
if(viewType.equals("ImageVIew")){
item(position).addView(new ImageVIew(context));
}
Good Luck
In your adapter class of your recyclerView,
in the onBindViewHolder method,
create another adapter and do the same methods for your new adapter.
The hierarchy will be,
mainRecyclerView -> item1(->childRecyclerView1) , item2(->childRecyclerView2), item3(->childRecyclerView3)
This way you can achieve what you want without wrong values to be viewed on wrong items.
You should take any Empty Layout like Linearlayout in your child item layout XML and then add views into that LinearLayout of your particular item in this way when you scroll List all of you child views which you have added to LinearLayout also scroll with that item .

Categories

Resources