I am using the PagedList library in my app.
It all works as expected, using the PagedListAdapter. However, I am not able to find how I can get a callback and be notified that the PagedList has been updated.
At list's ItemKeyedDataSource is used to fetch the list's data from the network. At that point, the PagedListAdapter's submitList is called, providing a PagedList of length 0. When the DataSource has fetched the data from the network, its callback.onResult() is executed and the list's UI is updated showing the fetched items. However, this does not call the submitList method, and I have not found a way to be notified in the adapter of this update, as the onCurrentListChanged is neither called. How can the Adapter be notified of such changes?
Thank you :)
You can use PagedList.addWeakCallback. You can pass a PagedList.Callback to it and listen to events such as onInserted, onChanged or onRemoved. More detail can be found in this answer
Related
I have a question that is more related to proper design and architecture of MVVM than to coding itself. In my project I have a situation that on ViewModel is suplying data, that are later used in RecyclerView.Adapter to create a proper view.
However I wonder if this would be also correct (from proper 'way of doing things' POV) if some of the data would be supplied in form of id's to be further fetched from Room or external server? For instance during onBindViewHolder use some LiveData with observe() to update certain fields on succesfull load.
Doing data fetch in the views is a no-go. It defeats the very purpose of MVVM and in particular the Android Jetpack efforts. One of the big reason is the data needs to survive configurations. Putting "data fetching" mechanism in the view defeats that as the view can be destroyed and recreated anytime when need be.
So I would suggest you make sure all calls to the network or any other source of data for that matter revolve around the ViewModel and never a view. Let the VM feed the data to the View through observer.
Exception to what I have just said is such use case as loading images with Picasso or Glide where by you feed them URL and they load image. But that's a different thing as they are designed to handle that.
UPDATE with followup Questions
it's ok to put observe() still inside Adapter, during the binding process?
No! Data sent to the adapter must be complete in the purpose it is supposed to serve. For example, if you have to do list app and your Top-Level Activity displays all Todos, then you must feed adapter will complete data (Title, Created time, et al).
The data that are not going to be displayed (like descriptions or sub-to-do-lists) and aren't necessary to identify specific to do should not be fetched (unless you want to store them for sole purpose of avoiding a second network call and pass them to the next activity as serialized data).
When user clicks specific To-Do, then launch new activity with its own ViewModel that will fetch details for that activity (assuming you passed some ID with intent).
If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?
Observe is a way to post data to the view when either it have changed or the old view was destroyed and so the new view is being given the same old data that survived the "view death". So it is in the same method where updating data of the Adapter should be done and hence call to notifyDataSetChanged() or similar methods.
I hope that makes it clear.
I think it's best to keep the ViewModel separate from the Adapter. From what I'm gathering you want to basically have each list item load it's own data from Room by having each list item observe on a particular ID for that item. This sounds rather inefficient as you're basically having each item execute a query on the database (or network call) to retrieve just one item for all items that are going to be displayed, think about how it will scale if you were displaying a 100 items. I think it's best that you just execute one query to get the list of data and set the data to the list items in the adapter, keep it simple. Note that onBindViewHolder() doesn't just get called once but multiple times when you're scrolling the screen, so it could get quite ugly if you're trying to lazily load every list item.
Now I read the Firebase documentation for android. I saw all the events onDataChange etc. But I didn't get 1 thing. Let's say I want to initialize my activity with some information from the database. No data has changed, no nothing. So it seems this event onDataChange won't help me. How can I explicitly say something like "go and fetch the data from firebase".
When you attach a ValueEventListener, its onDataChange method is called "immediately" with a snapshot of the current data.
If you attach the listener with addValueEventListener it will (in addition) be called upon any subsequent change to the data.
You could create a method that when the activity is initialized, so is a data structure with info from the database.
Retrieving data is pretty explicit here: https://firebase.google.com/docs/database/admin/retrieve-data
Also, you should be really careful because you should be using callbacks. If the activity needs to populate its views depending on that data, you should be aware of having that data before creating the views or trying to instantiate with null objects.
You can read the data only once, see:
https://firebase.google.com/docs/database/android/read-and-write#read_data_once
You "use the addListenerForSingleValueEvent() method to simplify this scenario: it triggers once and then does not trigger again."
I'm moving my application from sqlite to Firebase. Previously I would read N items from the DB to an arraylist and call notifyItemRangeInserted. Now the most convenient way to get data from Firebase delivers objects one by one. I was wondering if anyone benched the cost of calling notifyItemInserted for each list item. Is that fine or should I batch my loads? I'm displaying everything in a RecyclerView.
Based on the documentation for RecyclerView:
There are two different classes of data change events, item changes and structural changes. Item changes are when a single item has its data updated but no positional changes have occurred. Structural changes are when items are inserted, removed or moved within the data set.
notifyItemInserted : Notify any registered observers that the item reflected at position has been newly inserted
notifyItemRangeInserted : Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted
Its a bit tricky to quantify and compare these two.
Based on the functionality: Both of them seems to perform same operation. They don't alter the existing items binding but rather alter existing items position.
Based on impact after notify observer: Calling notifyItemInserted in short interval would trigger the registered observers frequently. If those observers are doing some heavy computation then notifyItemInserted for each item would be expensive.
However, if you do have list that is not sequential in a range (e.g. remove item 1, 2 and 4), or if you want to perform insertion and removal simultaneously, and have it animated accordingly or you frequently modifying the adapter's dataset, efficient way would be to implement DiffUtil which is available in support library.
Most of the time our list changes completely and we set new list to
RecyclerView Adapter. And we call notifyDataSetChanged to update
adapter. NotifyDataSetChanged is costly. DiffUtil class solves that
problem now. It does its job perfectly
You can find more information here.
I was looking at the answer for making a Custom CursorLoader, and was wondering that if the data changes would there will be a notification of the data changing so that the list updates?
I'm using the compatibility library loader framework. When content changes I just call this and everything gets updated:
getSupportLoaderManager().getLoader(YOUR_LOADER).onContentChanged();
You could call notifyDataSetChanged on your adapter so that the ListView will display the updated cursor content.
From the documentation:
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
What is the difference? The android documentation doesn't have a description for notifyDataSetInvalidated(). I was thinking maybe you call that function to notify all registered listeners, but use notifyDataSetChanged() to not notify them?
Changed means the data set changed. Individual items updated, or items were added or removed. Invalidated means the data source is no longer available.