I'm using recycler view with grid view (every item contains a grid) with thumbnail images after adding images in view it start uploading, and i need to change some values (status) in model at the time of start and finish upload.
What I'm currently doing is change values in model and call notifyDataSetChanged, but it causes to slow down application (hang some times) because it updating every child of list :(
Can any one help me with this?
Call notifydatasetchanged() for particular Gridview adapter only. Don't call notifydatasetchanged() for complete recyclerview adapter.
Try setting it again to the adapter.
gridview.setAdapter(your adapter)
-passing your new list to your adapter.
I believe notifyDataSetChanged updates all of your values regardless if they have a change or not that is why it is slow. I suggest you try using notifyItemChanged(int position) instead.
According to recyclerview docs this is what notifyDataSetChanged does:
This event does not specify what about the data set has changed, forcing any observers to assume that all existing items and structure may no longer be valid. LayoutManagers will be forced to fully rebind and relayout all visible views.
Old question but similar to what I'm experiencing.
RecyclerView is usually incredibly fast when changing list details. But manipulating a lot of images can really slow it down if you're not careul.
I'd recommend you try removing the images and repeat it so you can see if that is just as slow. I suspect this will be a lot faster.
If so, make sure you're handling the images off the UI thread. The Android docs recommend you use a library such as Glide which also handles sub-sampling the image to make sure it's the right size.
Hope that helps!
Related
I know that notifydatasetchanged updates the whole list and it is recommended to use DiffUtil to only update changed items, what I am not understanding yet after a lot of research is if it's expected that notifydatasetchanged animates list items separately when being used in a RecyclerView. Because I have one RecyclerAdapter where I replace the dataset completely and then call notifyDataSetChanged leading to list items being animated perfectly, only those added get the default added animation, removed ones get the removed animation and so on. At the same time I have another adapter where I also do the same, call notifyDataSetChanged but here the whole list flashes shortly, I am not getting any animations automatically, so I don't know from what does notifydatasetchanged make this dependent? Both adapters are too complex and long to post here.
I found the answer here:
The framework will attempt to animate views if your adapter uses
stable IDs, which provides enough data to guess which views are
removed/added/etc
So I have an activity with RecyclerView and I want to change TextView of every item in the RecyclerView by pressing button that has onClickListener() in the activity.
I'm wondering what is better in terms of performance:
Use notifyDataSetChanged ones.
Use loop with condition like int i is less than List.size() where notifyItemChanged would be called few times.
In both cases I create boolean variable in RecyclerView Adapter which is used by onBindViewHolder to know how to update item. By default it's false and after button click it becomes true so onBindViewHolder updates item in different way.
Also I would like to know if this approach is suitable at all.
If you are simply updating one part of the view, use the notifyItemRangeChanged()or notifyItemChanged() instead of notifiyDataSetChanged(). The difference here has to do with structural changes vs item changes. This is on the android developers RecyclerView.Adapter documentation found here.
Here is another tidbit on the differences between the two types of changes:
There are two different classes of data change events, item changes
and structural changes. Item changes are when a single item has its
data updated but no positional changes have occurred. Structural
changes are when items are inserted, removed or moved within the data
set.
This is taken from that aforementioned page,
If you are writing an adapter it will always be more efficient to use
the more specific change events if you can. Rely on
notifyDataSetChanged() as a last resort.
So just to clarify use notifyDataSetChanged() as a last resort, and instead ask yourself if you can preform one of these methods instead, and if you can use it instead:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
which makes sense because notifyDataSetChanged() will pretty much try to redraw everything based on the data and make no previous assumptions on it, while the other methods will just look for changes. That means the adapter has to do a lot more work that is not necessary. This is what notifyDataSetChanged() does:
This event does not specify what about the data set has changed,
forcing any observers to assume that all existing items and structure
may no longer be valid. LayoutManagers will be forced to fully rebind
and relayout all visible views.
This also makes sense to use the incremental or range approach, because you are changing the text, you need to go get each new text and when you do that you should tell the adapter you changed it. Now, if you do a button click and get all new text values, and create a new list or something then call heavy notifyDataSetChanged().
I would definitely call notifyDataSetChanged() if all of the data items ar no longer valid. When you call notifyItemChanged(mPos), it is equivalent to a call to notifyItemRangeChanged(mPos, 1), and every time it is called, requestLayout() is also called. On the other hand, when you call notifyDataSetChanged() or notifyItemRangeChanged(0, mList.size()), there is only one call to requestLayout().
Your question should now be, what is better, a call to notifyDataSetChanged() or notifyItemRangeChanged(0, mList.size())? For that one I don't have an answer.
I've noticed that notifyItemChanged(mPos) triggers onBindVieHolder for corresponding position even it's currently not visible.
For me, calling it in a loop for all elements was more costly than notifyDatasetChanged which redrawn only visible ones.
So be careful with large datasets.
Background
I'm using the PinterestLikeAdapterView library to show some images from the internet, which is like a gridView but with different height for each cell.
The problem
Since I use this library to show images from the internet, it's crucial that when calling notifyDatasetChanged won't cause a mess on the views.
For some reason, calling this function would call the getView() method with different positions for the views. for example, even though i didn't scroll at all, and call notifyDatasetChanged (or addAll in case it's an ArrayAdapter), for position 0 it will take what was the view of position 8, for position 1 it will take the view of position 7 , and so on...
This makes the whole grid to refresh its images, and so it ruins the UX.
Usually, in both gridView and listView, the way to overcome refreshing is to put the position that was used for the view inside the viewHolder, and if they are equal, it means that they still match.
for example:
... getView(...)
{
//<=inflate a new view if needed
//avoid refreshing view in case it's still the same position:
if(position==holder.position)
return rootView;
holder.position=position;
//<=update the view according to its data
...
}
However, here they re-use other views in a different order so this trick won't work here.
Because of this issue, not only i get refreshes of almost all of the visible views, but since i use DiskCacheLru library, it crashes since it tries to put 2 identical inputSteam data into the same key using 2 threads.
The question
What can I do?
Is this a known bug in the library?
Maybe I'm using a bad way to overcome refreshes?
for now, i use memory cache to at least get items that were cached before, but that's more like a "cure" than a "vaccine"...
Short answer:
Use an image loading library like Picasso that caches most recently used images in memory, so they don't need to be reloaded from the network.
Long answer:
AdapterView does something called View recycling, where Views which are no longer needed to display a position are re-used to display another. (For example, as you scroll down, Views that disappear off the top of the screen are reused for new positions at the bottom of the screen.) Because of this, it's normal for getView() to be passed the same View for more than one position.
This is done for performance reasons: Inflating new Views is hard and takes time, so AdapterView tries to do it as infrequently as possible.
When using a holder, you store references to ImageView and TextView children inside the item's View, so you don't have to look them up with findViewById() each time - you don't usually store anything specific to a particular position, because the View and its holder will often be used for different positions.
Now, when you call notifyDataSetChanged(), AdapterView assumes that the data set has completely changed. The image that was associated with position 8 may no longer be present, or it may be associated with position 12 now. Consequently, all the existing Views are scrapped - but because AdapterView would still like to avoid inflating new Views, they're re-used to display the new data, with no regard for what position they were displaying previously.
This explains why getView() is being passed the same View for different positions, and why visible positions are being refreshed when you call notifyDataSetChanged(). But how to avoid having your images refresh, ruining the user experience?
Use an image loading library like Picasso that caches most recently used images in memory, so they don't need to be reloaded from the network. The refresh will still happen, but it'll be instantaneous.
View getView(int position, View view, ViewGroup parent) will be always called ascendingly, after notifyDataSetChanged().
I guess that, the order of finishing download task will cause this problem.
As you mentioned in your question, keeping the position is a good way to avoid this problem.
Here is another way to solve it, also re-use the imageviews.
Keep a weak reference of each ImageView in download task.
Then wrap the download task in a dummy ColorDrawable.
When getView is called, set the dummy ColorDrawable to ImageView, and start the download. When download is complete, set the downloaded image back to the referenced ImageView in OnPostExecute().
Explanation
http://android-developers.blogspot.jp/2010/07/multithreading-for-performance.html
Source code
https://code.google.com/p/android-imagedownloader/source/checkout
There is a very good example on PinterestLikeListView in GitHub
Here is the library StaggeredGridView
A modified version of Android's experimental StaggeredGridView. Includes own OnItemClickListener and OnItemLongClickListener, selector, and fixed position restore.
You can get library project here library
and you can get Demo project Here
This is very good open source project, so you can use instead of PinterestLikeAdapterView
Hope this library is going to help you out.
seems that the authors of this library have fixed it, after some time i've reported about it:
https://github.com/huewu/PinterestLikeAdapterView/issues/8
I have an adapter that displays a grid of thumbnails with a text. These thumbnails are heavy to load, heavy to draw, etc.
The thumbnail gridview is constantly filled with new content, let's say, 1 new item every 2 seconds.
My adapter has a function that I call from outside to inject new items:
public void postNew(Item i) {
arrayStuff.put(i);
notifyDataSetChanged();
}
What happens is, with my current approach, when I insert a new element in the gridview, it refreshes everything, even if the added item is not going to be visible. The refresh process kind of breaks the experience, specially if the user is browsing the gridview and new content arrives.
How would you recommend improving this? is there a lighter 'notifyDataSetChanged()' or something like that?
I do not know of any lighter version of notify data set, but you can always use ListView.getFirstVisiblePosition and ListView.getLastVisiblePosition to determine whether your latest added position is visible, and only call notifyDataSetChanged if it is.
As for "heavy" bitmaps, as heavy as it is I think you should resample or scale it to the minimum size you need, using LruCache you can reduce the need of re-drawing on notify data set changed.
It sounds like you probably need to implement some form of caching, it's not very good memory management to have images which are not visible loaded into memory, ideally you would retrieve them from cache when they become (or are about to become) visible.
An alternative approach could be to add some form of visual indicator when new content arrives and then implement "pull down to refresh" or similar, then make a call to notifyDataSetChanged() on your adapter to refresh the content. I can imagine that refreshing every couple of seconds would not give a great UX because it would be hard to follow if the screen content is constantly changing.
You need create custom view(dynamic at runtime) that adds multiple imageview and appropriate textview, the container view should be LinearLayout, after that you can able to update a particular view or element.
Is there a way to call BaseAdapter.notifyDataSetChanged() on a single element in the adapter.
What I am trying to do is update the data and reflect those changes in the containing ListView. The problem is that sometimes the change is so small that it seems ridiculous that I have to refresh the whole view rather than the single item in the view that has been updated.
I am not aware of such method. If it's really important, you can always find individual item view to update. But I don't think that it worth it as Android is pretty efficient in updating list views. So it will not do much extra work (definitelly not going beyond items currently visible on the screen).