I'm using Paging 3 to load a list of items from room database but that slow down my app and UI looks very leggy
Here a look how i am using it
mViewModel.messages.observe(viewLifecycleOwner) {
mMessagesAdapter.submitData(viewLifecycleOwner.lifecycle, it)
}
How can I fix it?
Related
I have a Flow of PagingData and I need to check if this flow contains specific Item value.
I know that I cant collect it by .collectAsLazyPagingItems().itemSnapshotList.items, but it is possible only in #Composable. How can I do it or though get the List of Item?
I have tried like this:
viewModelScope.launch {
itemsFlow.onEach {
it.map { item ->
if (item == myItem) {
//do something
}
}
}.collect()
}
but it does nothing.
PagingData does not hold any state, it's only used to hook up paging to something that can actually collect and present the state, like PagingDataAdapter or in LazyPagingItems using the method you've already found.
Another way to think about it is that it is impossible to inspect whether a paginated list contains an item, without first loading all the items, and currently the only way to realize the state in Paging is by collecting it in the UI layer.
Similarly, PagingData.map does not eagerly load the entire list, it operates incrementally as items are loaded in to be displayed in the UI. So you can use it to check if / when a specific item is loaded, but you cannot use it to search the list since it only runs as items are loaded for the UI.
I'm making an API call getData(forPage: Int): Response which returns a page-worth of data (10 items max) and thereIsMoreData: Boolean.
The recyclerView is implemented that by scrolling, the scroll listener automatically fetches more data using that API call:
val scrollListener = object : MyScrollListener() {
override fun loadMoreItems() {
apiFunctionForLoading(currentPage + 1)
}
}
The problem is that with longer screen devices that have more space for items (let's say 20), the RV receives 10 items and then doesn't allow scrolling, because there's no more items to scroll to. Without scrolling, more data cannot be loaded.
My naive solution:
load first set of data
if thereIsMoreData == true I load another page of data
now I have more data than the screen can display at once hence allowing scroll
Is there a more ellegant solution?
Android has this Paging Library now which is about displaying chunks of data and fetching more when needed. I haven't used it and it looks like it might be a bit of work, but maybe it's worth a look?
Codepath has a tutorial on using it and I think their stuff is pretty good and easy to follow, so maybe check that out too. They also have this older tutorial that's closer to what you're doing (handling it yourself) so there's that too.
I guess in general, you'd want your adapter to return an "infinite" number for getItemCount() (like Integer.MAX_VALUE). And then in your onBindViewHolder(holder, position) method you'd either set the item at position, or if you don't have that item yet you load in the next page until you get it.
That way your initial page will always have the right amount of content, because it will be full of ViewHolders that have asked for data - if there's more than 10, then item 11 will have triggered the API call. But actually handling the callback and all the updating is the tricky part! If you have that working already then great, but it's what the Paging library was built to take care of for you (or at least make it easier!)
An elegant way would be to check whether the view can actually scroll down:
recyclerView.canScrollVertically(1)
1 means downwards -> returns true if it is possible tro scroll down.
So if it returns false, your page is not fully filled yet.
This is just a mock to give you some idea of what I am doing.
My recycler view has complex logic. Let me point out them,
View holder UI is complex.
Loading banner ads after each 5 view holders.
My data is coming from network and I have been using Room + Retrofit + Paging adapter.
User experience is very bad. I need some suggestions. I believe there are 2 things effecting my scrolling function.
xml ui inflation.
Loading admob ads in UI thread.(They want us to do it in ui thread. still I dont know why they do this crazy stuffs. )
I need some suggestions how can I improve and give some good user experience .
Since there is no source code, I'm unable to try to evaluate the exact cause of this. But I experienced something like this before, I have some possible solution for you.
1) Move long-running task away from UI thread:
I noticed that your data is coming from Room + Retrofit. By default, Room must operate in async manner, unless allowMainThreadQueries() is called. If you did called allowMainThreadQueries(), you can check your code if you accidentally trying to fetch data on UI thread.
2) Did you implement RecyclerView (RV) properly?
RV reduces the amount of xml inflation whenever possible and improve performance by reusing the inflated layout for the same view type. So, if you only have 2 view types as shown in your question, RV will only inflate 3-6 of your layout (even if you have e.g. 100 items in your list) and attaches it to ViewHolder and then bind and recycle the view with Item as you scrolled through the list.
However, RV may perform poorly if implemented wrongly. One example I experienced before is returning position as view type in RV adapter.
public class SampleAdapter extends RecyclerView.Adapter<RVItem.ViewHolder> {
private final List<Item> items = new ArrayList<>();
#Override
public int getItemViewType(int position) {
return position; //Never do this
return items.get(position).getType(); //Do this
}
}
So, you can try to check if any of your implementation/logic is wrong with RV.
3) Use Profiler in Android Studio
If none of the above suggestions resolve the issue. The last way I can think of is recording the trace using Profiler while u scrolling through the RV and trying to identify which call is time-consuming/blocking by analysis the trace.
More info: https://developer.android.com/studio/profile/android-profiler
Additional info
Lastly, may I know what do you mean by loading AdMob ads in UI thread. As far as I know, AdMob load and return ads asynchronously like UnifiedNativeAd. And only then you try to inflate and set your view using the data from UnifiedNativeAd on UI thread which is not really UI-blocking task
I am using paging library and everything is working good when I am directly returning DataSource.Factory<Int, Data> from Dao and then setting my paged list like this
pagedList = LivePagedListBuilder(dao().getAllData(), config).build()
When I click on my list item, it changes, updates the room database and with paging automatically updates the UI. But I need to use a custom data source. So I created one and now I am setting my paged list like this
pagedList = LivePagedListBuilder(customDatasourceFactory, config).build().
The problem is that now when I update a row in my room table, the UI is not automatically updated. I am also using invalidate() when my dataset is changed. But in this case when I want to update only a single item, I can not use it, because my positional datasource then loads the first page and when I am in the middle of my list, alter list item, it jumps to the top.
So basically what I want is to implement the same behaviour that I had when I was returning directly Datasource Factory from Dao, but now with my own datasource.
Currently, I'm using AsyncTask to handle Http connection and retrieve data as JSON format.
Loading all data is trivial but it consumes too much time, so I decided to switch to load 10 items at a time using LIMIT OFFSET (mysql).
Next I set up the event onScroll for my list view to create a new AsyncTask each time user scroll. However, from what I read, AsyncTask is stored in a thread pool which is limited 5 threads at a time, so I'm not sure this is a right approach. I'm newbie to client/server app, so could I anyone give me an advice on this issue? Any related article, documentation would be greatly appreciated.
Here are few useful links for it,
Android: Implementing progressbar and "loading..." for Endless List like Android Market
Endless Listview with current Async Task
Android Endless List
http://www.androidguys.com/2009/10/21/tutorial-autogrowing-listview/
http://mylifewithandroid.blogspot.com/2010/03/progressively-loading-listviews.html
In simple steps,
As user scrolls – detect the end of the list 1)Display a progress
notification 2)Ask for update 3)Receive update (asynchronously) and
extend list
A typical approach would be e.g. to load 25 initially and then have a footer in the list that displays e.g. the current count and the total count and upon pressing loads another 25 and so on. That would be a paged sort of loading.
When you do that you have to keep the current position and notify the adapter that the list has changed.
If you are using a ListView, I believe I can safely assume that you must be using some sort of ListAdapter. Instead of starting a new AsyncTask in the onScroll event, you should maintain just one single AsyncTask to retrieve data from the server, add that data to the ListAdapter dataset and then call notifyDatasetChanged on the ListAdapter.
The ListAdapter and ListView will take care of the rest.