So, I've playing with Android's new Paging Library part of the JetPack tools.
I'm doing a very basic demo App where I show a list of random user profiles that I get from the RandomUser API.
The thing is that I have everything set and It's actually kinda working.
In my the list's fragment I'm listening for the DB changes:
...
mainViewModel.userProfiles.observe(this, Observer { flowableList ->
usersAdapter.submitList(flowableList) })
...
(I tried with both, using Flowables
and Observables
with the RxPagedListBuilder
and now I'm using LiveData
with the LivePagedListBuilder
. I'd like to use the RxPagedListBuilder
if it's possible)
In my ViewModel I'm setting the LivePagedListBuilder
...
private val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(UserProfileDataSource.PAGE_SIZE / 2) //Is very important that the page size is the same everywhere!
.setPageSize(UserProfileDataSource.PAGE_SIZE)
.build()
val userProfiles: LiveData<PagedList<UserProfile>> = LivePagedListBuilder(
UserProfileDataSourceFactory(userProfileDao), config)
.setBoundaryCallback(UserProfileBoundaryCallback(randomUserRepository, userProfileDao))
.build()
...
I've set the DataSource to fetch the data form my DB (if there's any):
class UserProfileDataSource(val userDao: UserDao): PageKeyedDataSource<Int, UserProfile>() {
companion object {
const val PAGE_SIZE = 10
}
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, UserProfile>) {
callback.onResult(userDao.getUserProfileWithLimitAndOffset(PAGE_SIZE, 0), 0, PAGE_SIZE)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, UserProfile>) {
callback.onResult(userDao.getUserProfileWithLimitAndOffset(PAGE_SIZE, params.key), params.key + PAGE_SIZE)
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, UserProfile>) {
//This one is not used
}
}
And I've set the BoundaryCallback to fetch the data from network if the database is empty:
class UserProfileBoundaryCallback(val userRepository: RandomUserRepository,
val userDao: UserDao) : PagedList.BoundaryCallback<UserProfile>() {
/**
* Database returned 0 items. We should query the backend for more items.
*/
override fun onZeroItemsLoaded() {
userRepository.getUserProfiles(0, UserProfileDataSource.PAGE_SIZE)
.subscribeOn(Schedulers.io()) //We shouldn't write in the db over the UI thread!
.subscribe(object: DataSourceSubscriber<List<UserProfile>>() {
override fun onResultNext(userProfiles: List<UserProfile>) {
userDao.storeUserProfiles(userProfiles)
}
})
}
/**
* User reached to the end of the list.
*/
override fun onItemAtEndLoaded(itemAtEnd: UserProfile) {
userRepository.getUserProfiles(itemAtEnd.indexPageNumber + 1, UserProfileDataSource.PAGE_SIZE)
.subscribeOn(Schedulers.io()) //We shouldn't write in the db over the UI thread!
.subscribe(object: DataSourceSubscriber<List<UserProfile>>() {
override fun onResultNext(userProfiles: List<UserProfile>) {
userDao.storeUserProfiles(userProfiles)
}
})
}
}
The thing is that the Flowable
, Observable
or LiveData
in the UI only gets triggered if the data is coming from the database, when the UserProfileDataSource
returns some result from database. If the UserProfileDataSource
doesn't return any result, the BoundaryCallback gets called, the request to the API is successfully done, the data gets stored in the database, but the Flowable
never gets triggered. If I close the App and open it again, is gonna fetch the data that we just got from the database and then is going to display it properly.
I've tried every possible solution that I saw in all the articles, tutorials and github projects that I searched.
As I said I tried to change the RxPagedListBuilder
to a LivePagedListBuilder
.
When using RxPagedListBuilder
I tried with both, Flowables
and Observables
.
I tried setting different configurations for the PagedList.Config.Builder
using different Schedulers.
I'm really out of options here, I've been all day long struggling with this, any tip will be appreciated.
If you need more info about the code, just leave me a comment and I'll update the post.
Update:
I've submitted the full source code to my Github repo
The Paging Library lets you load data directly from your backend using keys that the network provides. Your data can be uncountably large. Using the Paging Library, you can load data into pages until there isn't any data remaining. You can observe your data more easily.
The Paging library helps you load and display pages of data from a larger dataset from local storage or over network. This approach allows your app to use both network bandwidth and system resources more efficiently.
Okay, I found the error:
I just removed the UserProfileDataSourceFactory
and the UserProfileDataSource
classes. Instead of that I added this method to my DAO:
@Query("SELECT * FROM user_profile")
fun getUserProfiles(): DataSource.Factory<Int, UserProfile>
And I'm building my RxPagedListBuilder
like:
RxPagedListBuilder(userDao.getUserProfiles(), UserProfileDataSource.PAGE_SIZE)
.setBoundaryCallback(UserProfileBoundaryCallback(randomUserApi, userDao))
.buildObservable()
And that's it. I'm not sure why it doesn't work with the DataSource and the DataSourceFactory, if anyone can explain this oddly behaviour I'll mark that answer as accepted since my solution was more like a lucky shot.
One thing that is happening now though, is that when I load the data into the adapter, the adapter scrolls back to the top position.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With