I am loading items from API using the Paging 3 library. I would like to add an item to a specific position in the recyclerview.
If I am using normal adapter I would just use
mArrayList.add(position, item);
notifyItemInserted(position);
But I am using the PagingDataAdapter and I have tried this
val newPostResponse = response?.responseBody
homeAdapter.snapshot().items.toMutableList().add(0, newPostResponse)
homeAdapter.notifyItemInserted(0)
I have also tried
val newPostResponse = response?.responseBody
homeAdapter.snapshot().items.toMutableList().add(0, newPostResponse)
homeAdapter.notifyDataSetChanged()
None of these is working. Someone help me know the right way to do it.
This seems to be a duplicate of How to update single item using Paging 3 library.
You must go through the .invalidate() loop to maintain a single source of truth and make Paging aware of your local changes. This is currently the only supported way to add an item to Paging.
Related
Problem Statement: Using Paging3 library and need to show loading information in header when user swipes to refresh. I followed the sample provided in the here.
Solution: Tried adding refresh header in LoadStateAdapter for with using pagingAdapter.withLoadStateHeaderAndFooter().
messagesRecyclerView.adapter = messagesAdapter.withLoadStateHeaderAndFooter(
header = MessageHeaderStateAdapter(messagesAdapter),
footer = MessageLoadStateAdapter()
)
But it does not shows the header until I swipe it 5-6 times in quick successions.
Footer, on other hand, shows just fine with loading more and error layouts based on LoadState.
Error State
Loading More State
Any pointers as to how this can be achieved are much appreciated.
withLoadStateHeader just returns a ConcatAdapter that listens to PREPEND state. If you want the header to show up during REFRESH you can basically copy the code out of that helper, but instead update LoadStateAdapter based on REFRESH LoadState using a listener on your PagingDataAdapter
val header = MyLoadStateHeader()
adapter.addLoadStateListener { loadStates ->
header.loadState = loadStates.refresh
}
recyclerView.adapter = ConcatAdapter(header, this)
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.
Setup :
We are using PagedListEpoxyController, RxPagedListBuilder, ItemKeyedDataSource with a runtime Mockgenerator which just creates a user with respect to the index of data that is generated, for eg. user1, user100, user500 etc.
Paging configuration is :
val pageSize = 50
val initialLoadSizeHint = 80
val prefetchDistance = 16
Expected behaviour
Whenever we scroll down to the 400th item and come back to the 300th item and invalidate the list by clicking Follow/Unfollow the UI jumps. When the button is clicked the PagedList is invalidated like this:
followers.dataSource.invalidate()
loadInitial() inside ItemKeyedDataSource should be called with the value of params.key so we can get the same page that was previously displayed to be re-displayed. For example if user300 was visible in the screen, that item requestedInitialKey should be in the range of [user221,user280].
Where is the issue?
Paging Library tracks of the last item that was fetched in the pagedList before invalidation but does not provide a way to know which was the last item/range that was visible in the screen just before invalidation.
After invalidating the pagedList, The first callback which gets called after pagedList invalidation is loadInitial(params : LoadInitialParams, callback : LoadInitialCallback) with the params only holds the information about pageSize and placeholder along with requestedInitialKey as last item that was fetched before invalidation which is very misleading. Why, after invalidation a pagedList does request the last item to be fetched in the list as the requestedInitialKey / item in the firstPage rather than the last item that was displayed before invalidation?. Sorry if I have totally misunderstood this thing but couldn't really make sense of it.
Question :
I wish pagedList to start fetching the first page in new PagedList after invalidation from the previous scroll Location rather than the last item in the previous pagedList.
But if PagedList is not supposed to work this way then How can I know any of the item/index that was previously visible in the screen before invalidation?
If I can't do that currently either, what is the recommended way to achieve this requirement?
This issue can be found here :
To reproduce : Scroll down far enough (like 60th item) and press "Refresh" you will see the jump in the list.
Link to github issue
The way we solved this issue is, UI only listens from Room Database as a single source of truth. This issue is not present in that setup. But when we use ItemKeyedDatasource, this issue is persistent even until now.
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.
I have data that was insert PostKey and Rating by cloud function. Following this picture newfeed, post
and I use FirebaseIndexRecyclerAdapter to get data
FirebaseIndexRecyclerAdapter<Post, FeedFragment.PostViewHolder> firebaseIndexRecyclerAdapter = new FirebaseIndexRecyclerAdapter<Post, FeedFragment.PostViewHolder>(
Post.class,
R.layout.post_row,
FeedFragment.PostViewHolder.class,
FirebaseRef.mNewfeedRef.child(UID),
FirebaseRef.mPostRef
) {
Solution that I was thinks is
First one is orderByValue() and load StartAt(0) to EndAt(5) when scrolled to bottom it called StartAt(0) to EndAt(10) and so on but each time will load old data that could wasted traffic.
Second one is create new reference suppose is "newfeed-post" that have only top 5 items ordered by rating that get from "newfeed" and when I scroll down to load more I request to cloud function to let cloud function insert more 5 items from "newfeed" to "newfeed-post" that lower rating (eg. position 6-10).
Please help and let me know how to do this. Thanks :D
Pagination is not supported by FirebaseUI-Android components. They currently require you to provide a single query that yields all the results to display. There is no way to progressively supply more queries as a list is scrolled. Also see the discussion in this issue on the Github repo for FirebaseUI-Android.