Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paging Library: Saving data in the DB doesn't trigger the UI changes

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

like image 755
4gus71n Avatar asked May 29 '18 19:05

4gus71n


People also ask

What is Paging library?

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.

What is Paging data?

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.


1 Answers

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.

like image 157
4gus71n Avatar answered Nov 28 '22 01:11

4gus71n