What's the simplest way to invalidate data retrieved by the paging library?
I need to do that on logout.
I've a DataSource.Factory and a DataSource that extends PageKeyedDataSource. Is it a good idea to call invalidate() on DataSource contructor? Will it work as expected?
Assuming you are using the Paging library with other Architecture Components, such as LiveData and LifecycleOwner and your screen is closed on logout, the DataSource will be invalidated automatically.
If you don't, you still can invalidate it manually, calling
yourDataSource.invalidate();
After the invalidation, you won't be able to continue using the DataSource. However, the views from the adapter will not be detached automatically, so if you want to clear them, you should either provide from your DataSource.Factory a DataSource that will return no data either just clear the RecyclerView.Adapter manually.
Related
I'm using the RemoteMediator in an app to load page keyed data. Everything works fine, except when after process death, the data is refreshed.
My current implementation is :
val results = savedStateHandle.get<String>("query").flatMapLatest { query ->
repository.getPager(
query = query,
)
}.cachedIn(viewModelScope)
I do know about the initialize() function of RemoteMediator, but how do I tie it in with process death?
As you found out, .cachedIn just operates in memory, so it won't survive process death. You cannot rely on Paging's internal cache of items in memory for this, you need to cache the loaded items on disk.
I would recommend using something like Room or some dedicated persistence layer that is actually built to handle large lists of arbitrary data classes.
I would not recommend to try to serialize and stash the entire list of data into SavedState as this could become prohibitively expensive quite quickly.
For your other point on RemoteMediator - it is just a "dumb" callback which has no influence on what Paging actually loads or displays. It's simply a way for your to write custom logic which is triggered during edge-case conditions in Paging. You probably only want this if you are already using a layered approach and trying to skip remote REFRESH. If that is your case, the RemoteMediator.intiailize function is guaranteed to complete before Paging starts loading, which means you can check whether you are coming from SavedState and there is already cached data, and if so, you can skip remote REFRESH by returning InitializeAction.SKIP_INITIAL_REFRESH.
I'm making a reactive app with a lot of data using Android MVVM. There are a lot of cases where I push data to firestore documents in my app, but i also want this change in data to be reflected locally in my app. So I also add the data to the local copy (data class) of the firestore document. I do this in my viewmodel.
The problem here is that I won't see change until I restart the app. So I need a way to call adapter.notifyDataSetChanged() from my viewmodel so the recyclerviews are updated immediately. Whats the best way to do this? Please help.
As you change and update data frequently, adapter.notifyDataSetChanged isn't helpful in this case because even if you update one item it will update the whole list even when the rest not touched!
So A better way to make your adapter
1- extend ListAdapter<data_model, view_holder>(diff_callback).
this Uses AsyncListDiffer under the hood to calculate and update the changed items only
2-whenever you need to update your data in viewmodel call:
adapter.submitList()
For more information on how to do that:
check doc: https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter
Yours ViewModel should't call adapter.notifyDataSetChanged() directly for two reasons:
Adapter is Android dependency. And if you want to write unit tests to yours view model you should avoid use android framework dependencies
I think adapter.notifyDataSetChanged() is UI logic implementation detail. And will be better if we move this logic to our view layer (fragment/activity/custom views)
You could solve this problem by encapsulate notifyDataSetChanged logic in yours adapter like this:
// Observe live data changes and set it to yours adapter
viewModel.state().observe(this, Observer {
newData: List<....> ->
adapter.setData(newData)
})
// Encapsulate notifyDataSetChanged at yours adapter:
class YoursAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun setData(data: List<.....>) {
......
notifyDataSetChanged()
}
}
In this case it's very easy to change adapter implementation from notifyDataSetChanged() to more precise updates like notifyItemInserted() later for example.
But I'm also recommend do not use notifyDataSetChanged() function and prefer DiffUtil. Or use libraries that can help with recycler view boilerplate and already have DiffUtil support under the hood. For example I like to use Groupie in my projects
Consider we have some Disposable in our RecyclerView.Adapter and we added them to a CompositeDisposable.
which Adapter method callback is the best choice to clear() the CompositeDisposable ?
Currently I did on the onDetachedFromRecyclerView. I want to be sure about how correct is this.
It would be easier to answer if you could provide the code of the adapter.
In general, Disposable should be disposed with respect to your business logic and the component containing lifecycle.
I would also say that it is better to not use Rx inside of RecyclerView adapter. Here are the benefits:
Adapter logic remains simple and synchronous.
You don't need to think about possible lifecycle or multithreading issues when developing the adapter.
Rx streams always allocate a lot of memory, so (if we talk about RecyclerView) putting them into the wrong place can lead to performance issues.
So, I'd suggest moving Rx streams into Presenter/ViewModel or similar component.
I use Android Architecture Components to build my app. There is Paging Library to load items with Room generated DataSource. Also there is BoundaryCallback to get new data from server and store it in the database. It works fine, all is reactive, changes in the database come into PagedList.
But now I need to these items get some additional data, some calculations before they come into PagesList and RecyclerView. These calculations is not so fast to executing them on main thread in RecyclerView ViewHolder (actually I need to get additional data from the database or even from the server). So I supposed that I need to write my custom DataSource and make calculations there and then pass these processed items to PagedList.
I created my ItemKeyedDataSource (I'm not sure this is correct, because I load data from database, but this data source type is designed for network, but I don't think this is critical), and make queries in Dao that return List of items. After I got a "page", I make calculations to items and then pass it to callback. It works, PagedList gets processed items.
But unfortunately there is no reactivity with this approach. No changes in database come to my PagedList. I tried to return LiveData<List> from Dao and add observeForever() listener in DataSource but it fails since you can't run it on background thread.
I watched Room generated DataSource.Factory and LimitOffsetDataSource but it doesn't look good to me since you need to pass table names to observe changes and other unclear things.
I suppose that I need to use invalidate(), but I don't because I have no idea where it should be.
There is 3 main questions:
Is it right to process items in DataSource before they come to RecyclerView or there is a better place?
Should I use PositionalDataSource instead of ItemKeyedDataSource?
How can I add Room reactivity to custom DataSource?
It seems that I've found a mistake in my DataSource.Factory. Instead of creating DataSource object in create() method I just returned object which was passed to that factory (I saw it in one popular article on Medium). And because of that I couldn't invalidate my DataSource. But now I create DataSource in that method and invalidation works.
The only problem is to understand where and when to invalidate. For now I've found some workaround: make a query in Dao that returns LiveData of last item, and then observe it in my Activity to understand that data was modified and call invalidate(). But I'm not sure this is a good solution. Maybe you know a better one.
You may add invalidationTracker in your DataSource:
dbRoom.getInvalidationTracker().addObserver(
object : InvalidationTracker.Observer("your_table") {
override fun onInvalidated(#NonNull tables: Set<String>) {
invalidate()
}
})
I have been implementing the new Paging Library with a RecyclerView with an app built on top of the Architecture Components.
The data to fill the list is obtained from the Room database. In fact, it is fetched from the network, stored on the local database and provided to the list.
In order to provide the necessary data to build the list, I have implemented my own custom PageKeyedDataSource. Everything works as expected except for one little detail. Once the list is displayed, if any change occurs to the data of a list's row element, it is not automatically updated. So, if for example my list is showing a list of items which have a field name, and suddenly, this field is updated in the local Room database for a certain row item, the list does not update the row UI automatically.
This behaviour only happens when using a custom DataSource unlike when the DataSource is obtained automatically from the DAO, by returning a DataSource Factory directly. However, I need to implement a custom DataSource.
I know it could be updated by calling the invalidate() method on the DataSource to rebuild the updated list. However, if the app is showing 2 lists at a time (half screen each for example), and this item appears in both lists, it would be needed to call invalidate() for both lists separately.
I have thought with a solution in which, instead of using an instance of the item's class to fill each ViewHolder, it uses a LiveData wrapped version of it, to make each row observe for changes on its own item and update that row UI when necessary. Nevertheless, I see some downsides on this approach:
A LifeCycleOwner (such as the Fragment containing the RecyclerView for example) must be passed to the PagedListAdapter and then forward it to the ViewHolder in order to observe the LiveData wrapped item.
A new observer will be registered for each list's new row, so I do not know at all if it has an excessive computational and memory cost, considering it would be done for every list in the app, which has a lot of lists in it.
As the LifeCycleOwner observing the LiveData wrapped item would be, for example, the Fragment containing the RecyclerView, instead of the ViewHolder itself, the observer will be notified every time a change on that item occurs, even if the row containing that item is not even visible at that moment because the list has been scrolled, which seems to me like a waste of resources that could increase the computational cost unnecessarily.
I do not know at all if, even considering those downsides, it could seem like a decent approach or, maybe, if any of you know any other cleaner and better way to manage it.
Thank you in advance.
Quite some time since last checked this question, but for anyone interested, here is the cause of my issue + a library I made to observe LiveData properly from a ViewHolder (to avoid having to use the workaround explained in the question).
My specific issue was due to a bad use of Kotlin's Data Classes. When using them, it is important to note that (as explained in the docs), the toString(), equals(), hashCode() and copy() will only take into account all those properties declared in the class' constructor, ignoring those declared in the class' body. A simple example:
data class MyClass1(val prop: Int, val name: String) {}
data class MyClass2(val prop: Int) {
var name: String = ""
}
fun main() {
val a = MyClass1(1, "a")
val b = MyClass1(1, "b")
println(a == b) //False :) -> a.name != b.name
val c = MyClass2(2)
c.name = "c"
val d = MyClass2(2)
d.name = "d"
println(c == d) //True!! :O -> But c.name != d.name
}
This is specially important when implementing the PagedListAdapter's DiffCallback, as if we are in a example's MyClass2 like scenario, no matter how many times we update the name field in our Room database, as the DiffCallback's areContentsTheSame() method is probably always going to return true, making the list never update on that change.
If the reason explained above is not the reason of your issue, or you just want to be able to observe LiveData instances properly from a ViewHolder, I developed a small library which provides a Lifecycle to any ViewHolder, making it able to observe LiveData instances the proper way (instead of having to use the workaround explained in the question).
https://github.com/Sarquella/LifecycleCells