Updating PagedListAdapter after initial load to show latest network data
Asked Answered
E

1

11

I'm utilizing the new Paging library to load paginated data from a rest API, but I'm having trouble understanding how to continue loading fresh data after it's cached in Room. Currently, I have a PagedListAdapter that's updated by using a boundary callback to load items from API, which are then stored in the local DB (Room), which are emitted to the UI via a flowable.

onZeroItemsLoaded and onItemAtEndLoaded calls from the BoundaryCallback are all functioning as expected, but only for the initial load. Once the data is retrieved once and stored in cache (Room), I'll never get fresh data from the network service--there's already data in the cache and the boundary callbacks are not triggered.

How do you mark items stale using the paging library so that every time the app opens my data is fresh?

ItemCallback.kt:

class ItemBoundaryCallback : PagedList.BoundaryCallback<Item>() {
        override fun onZeroItemsLoaded() {
            itemsFromNetwork(0)
        }

        override fun onItemAtEndLoaded(itemAtEnd: Item) {
            itemsFromNetwork(itemAtEnd.rank)
        }
    }

ItemRepository.kt:

fun items(): Flowable<PagedList<Item>> {
    return RxPagedListBuilder(database.itemDao().items(), pageSize)
            .setBoundaryCallback(boundaryCallback)
            .buildFlowable(BackpressureStrategy.LATEST)
}

ItemDao.kt

@Query("SELECT * FROM item ORDER BY rank ASC")
fun items(): DataSource.Factory<Int, Item>

ItemAdapter.kt

class ItemAdapter() : PagedListAdapter<Item, ItemViewHolder>(diffCallback) {
    companion object {
        private val diffCallback = object : DiffUtil.ItemCallback<Item>() {
            override fun areItemsTheSame(old: Item, new: Item): Boolean = old.id == new.id
            override fun areContentsTheSame(old: Item, new: Item): Boolean = old == new
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        return ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.r_item, parent, false))
    }

    override fun onBindViewHolder(viewHolder: ItemViewHolder, position: Int) {
        viewHolder.bind(getItem(position), clickListener)
    }
}

ItemFragment.kt

override fun onStart() {
    super.onStart()

    itemRepository.items()
            .subscribe(itemAdapter::submitList)
            .addTo(disposables)
Eufemiaeugen answered 26/6, 2018 at 3:49 Comment(0)
A
4

The whole point of using this technique ( database and rest) is caching data and preventing to load them again. But if your data is not static and it may be changed or invalidated. you can have two approaches:

1 - Remove every record in the database after closing the app (or when starting the app. destroy callbacks are not viable), in this case, you will get fresh data after next start.

2 - Make a field in your database as "Expiration" date or something similar. keep checking the field on onItemAtFrontLoaded in boundaryCallback if they're expired you can invalidate and fetch it again from web service.

Adrianneadriano answered 5/7, 2018 at 7:2 Comment(6)
Thanks for your answer. Can you provide an example of approach number 2? Specifically the invalidation logic? The data is timestamped, so it's easy enough to do a comparison.Eufemiaeugen
That works too. You just need to see if the record is new or not. If not, send a network fetch request in onItemAtFront method. There's no special logic. Do the thing that fit your project.Adrianneadriano
If you're looking for a more advance technique you can put a hash field in your server database and everytime you change the field calculate the new hash. In your client everytime user open the listView compare all the local hashes with server's with a network request. And update the one with diffrent hashes.Adrianneadriano
This is pretty ridiculous and not well explained in the docs. What kind of crazy app has conditions where you don't need to "refresh" data every launch. There is this whole convoluted live data mechanism to get the swipe-to-refresh to work as expected, and no way to call it manually because it is tied to the initial load which will always just fetch from the db.Trinetta
@DanielWilson The question is not about updating our database with new entries, It's about updating the ones we already have. For example, updating likes & comments for a post in social media. You have the posts in the database and you will receive new ones either with polling the new posts on-demand ( pull to refresh case ) or by pushing posts from the server. what you need is to figure out which posts(those you already have) need to update their likes or comments.Adrianneadriano
@FrostRocket btw why didn't you mark this as an accepted answer? is there anything else you wish to know?Adrianneadriano

© 2022 - 2024 — McMap. All rights reserved.