With a complex adapter, GridViews and ListViews can sometimes take a long time to populate after calling the setAdapter() method. Is there a method we can override that is triggered when the view has been successfully populated with data and is ready to be shown?
My feeling is that it isn't the setAdapter() that takes a long time, but rather that the complex adapter is doing something complex. The ListView and GridViews are specified to work rather quickly, but there's a few reasons they might be slower. Here's a few thoughts as to how to work this one out.
Figure out what's taking so long. Network calls, large database queries, and loading large files (Bitmaps in particular) are often the guilty party. If you don't have any of those, try profiling your code, and see what you can gleam from that.
Once you've figured out the culprit, see what you can do to decrease the amount of time. Perhaps shrinking the size of a bitmap, or using a better database query would work.
If you can't shrink the time down any more, then do the complex task in an AsyncTask. This will do the complex part of your code in the background, and only show the result when it is complete. This works well for things like network calls, loading bitmaps, etc.
Related
I have a RecyclerView (1.2.1), with a List adapter, and ViewHolder, backed by a Room PagingSource. There's about 700 items on the list. The paging seems to work fine, and I've flattened my View hierarchy as much as I can.
Upon initially loading the recycler view, everything seems fine. Paging works, everything seems snappy. onCreateViewHolder in my adapter is called 14 times, and initially 5 are visible on the screen.
Slower scrolling is fine (it does call onCreateViewHolder more often than I expected, but there's no jank).
The problem comes when rapidly flinging through the list. After 3-5 fast flings, it appears to decide that it needs to have more cached view holders, and makes many, many calls to onCreateViewHolder - this method is clocking in at ~5ms, but there's just too many of them, and the scrolling stops. It appears to call onCreateViewHolder ~700 times - the same as the number of items on the list, like it's not recycling the views at all.
At that point, sometimes the app will recover, and at that point everything is smooth and it doesn't appear to need to create more ViewHolders. Sometimes however I will get the ANR dialog.
I've tried tweaking the recyclerView.recycledViewPool.setMaxRecycledViews(), but this doesn't appear to to increase the recycledViewCount until after the mass onCreateViewHolder calls.
Is there anything I can do to resolve this? Make the fling speed slower? Tune the view holder recycling somehow so that it doesn't go nuts and try and create so many at once?
I don't think I can get the layout inflation any better, given my design and data constraints. And even if I could, it's still creating waaay too many to be able to get them done in under 16ms!
In my situation, I needed to do 2 things. First and foremost, I needed to adjust my paging configuration. My prefetchDistance was too small in relation to my page size! This got rid of the ANR - no more mass creation of ViewHolders!
The recyclerView would still pause scrolling when loading a page (especially towards the end). I added a loadStateFlow collector that handles showing a loading indicator so that the user knows there's more data coming.
In the beginning we had listViews. As a developer, we had to recycle and reuse the views to have a fluid experience.
Then, came the recylclerviews. Now, all the recycling heavy lifting is managed by android library itself.
Using pagination, an infinite recycler view can be implemented, which loads the data when needed.
But there is one problem I am still facing in infinite recyclerview. How is the data in the adapter managed?
In most of the infinite scroll implementations of recyclerview, the new data is appended to the original data. This makes the size of data set ever increasing.
Why cant dataset itself behave like recyclerview and recycle its data, instead of appending? (Like a circular queue).
How can one manage the positions of itemviews, when the dataset is a circular queue. Is it unnecessary and yields too little performance improvement? Am I missing some design pattern?
It would be possible to clear data already loaded, but this way you have to load data not only for "bottom" views, but for "upper ones" too. So user wants to back to previous data and he needs to load that data again, that's the problem.
There could be more work about notifying data set changed in recyclerView.
You can implement it this way, but remember about pagination and how it is implemented - for example limiting data in SQL statements standard way can output different data each call, so user backing to previous data can see other results! Check Twitter pagination for one of the way to achieve same results each time.
Pros:
less data in memory
Cons:
more work to write the code
more data loading for users (more internet usage, more loading times - when users wants back to previous data)
pagination has to be designed more carefully
I have a ListView/RecyclerView. Now, I want to add more information for each item and the information could be found in database according to the item value. How would you guy achieve it??
For the best practice, should I query database in the background thread? Should I cancel the query if user scrolling fast?
There is no "best practice" AFAIK, but the following is a short summary of your options:
Query on UI thread as the user scrolls
It is usually a very bad practice to execute queries on UI thread in general. It is a sin to do this in ListView.
That said, there are ORM libraries that perform lazy loading of nested objects on UI thread (e.g. GreenDAO). People use these libraries in applications that have ListViews and it even works alright for some of them.
I would strongly advice against this method.
Query of all items ahead of time
As #CommonsWare mentioned, for small to medium collections you can just load the entire dataset into memory on background thread and then bind it to the ListView. You will need to show some kind of progress indication while the data is being loaded.
The definition of "small" and "medium" is very vague, but I'd say that if you are sure that the dataset will not be larger than few MBs, then this method can work pretty well.
The drawback of this method is that the user will need to wait for the entire dataset to be loaded and bound to the ListView. Depending on the size of the dataset and the complexity of database scheme this might take a while.
Query a predefined number of items initially, and then perform additional queries as the user scrolls
This is the most complex scheme of all, but it is inevitable in some cases (e.g. "infinite list").
The idea is to get some predefined number of items into the ListView, and then track user's interactions in order to supply additional items.
For example, you can fetch 100 items initially, and then fetch additional 100 when the user scrolls to the end of the list.
An optimization of this scheme would be to fetch additional items before the user gets to the end of the list (let's say when he scrolled through 50 items). This allows to create a truly "infinite list" behavior.
Note that if your collection is very large, you will need to take care not only of adding a new items as the user scrolls, but also of removing "previous" items in order to avoid out of memory crash.
Query of additional information for items that are already shown
Sometimes you would like to perform some additional query after the item is already shown.
In this case, just perform the query in the background and bind the data to the item.
One caveat of this task is that if the view of the item is recycled (as is the case in RecyclerView by default), then when your background fetch is done the target View might show totally different item. Binding the returned data to this View would be a mistake.
One way of handling this is to cancel the fetch. However, this is cumbersome and error prone in practice.
The easier way is just set transientState flag when the query is initiated, and clear it when the query is completed.
I'm writing a notetaking application for Android, and I have a RecyclerView to display a grid of all the notes. Each note has a respective class called Note.
The note class knows/has:
The filename (Notes title)
The file path for the note
A loading method that reads and returns the text inside the file
The recyclerview reads from a ArrayList of Note. The list is populated by a scan of files in the App's note directory, and a Note object is created for each file. I'm trying to find the best way to load the text into the recyclerview, which displays a preview of the notes. I'm wondering if theres a best way, or least stupid way to do this. The ways I can think of doing this are:
Load every note's content at once during the initial scan and save it as a string in the note class in RAM, the downside being very high RAM usage with alot of notes and text.
Load the note's text in the Recylerview's onBindViewHolder method, but I'm worried if the user is scrolling through notes very fast, and a ViewHolder and its note are unbound before the text is loaded, there could be possible issues here.
Load each note's text in a threadpool, increases performance but same issue as above.
Is there a best way to do this, or a recommended/standard way to handle alot of files loading dynamically into a recyclerview?
Start off with the first one, then optimise as needed. If theyre just text files, they probably wont take up much memory. If you find they do take up too much memory or take too long to load you can look at strategies to fix that, but dont waste time optimising something before you know its a problem
How did you tacke The problem?
I am also making a same Kind of APP, I thing The best option is The second (loading filé contents on bindViewHolder) in an async maner (using asynctask, for example), it's the same BASIC ideia of imagem loaders like Picasso or Glide, you call them on Bind ViewHolder.
If having bad performance you could wrap The async call alongside a caching strategy.
I noticed today that the PagerAdapter gets called a large amount times. I counted 393 when scrolling pages slowly. I saw this question but it didn't really provide me with a good answer.
Is this normal behaviour
If so, why is it that the getCount method is called so often?
Just to be clear, I am looking for a more extensive answer then the one in the provided question.
I do also realize that I need to keep it as fast and that I have no control over how it is called, but that is not the question here.
As you concluded it is used a lot in onTouchEvent. OnTouchEvent is called whenever you interact with the screen, meaning touch move and release events. Moving just one pixel would result in a potential call to this method.
There is not much more to explain, it is just the way it is implemented. Usually adapter.getCount is implemented with something like List.getSize or Cursor.getCount. And has almost zero overhead. If this is a problem, optimize you ListAdapter.getCount method, cache the count or something like that. Only do complex stuff in there when needed and cache the result until it becomes invalid.