I'm working with the Room library. When I fetch some data, I get a List of Entities, that I present in a RecyclerView. The user is able to delete elements presented in that RecyclerView, which is implemented with a #Delete method in the DataAccessObject.
In order to also remove the deleted element from the RecyclerView, I also have to remove the element from the List of Entities mentioned earlier. And this what I don't like. I have to manually keep two data structures in sync.
Is there a way to link the RecyclerView directly to the a certain set of data in the database? Without an intermediate data structure I have to sync manually, thus when I delete an element in the database, this is immediately reflected in the RecyclerView.Adapter?
Room supports observable queries. So instead of returning list of entities in your dao object, return LiveData<List<Entity>> or one of the supported RxJava2 types and you'll get updates every time data is changed in the db, e.g. when item is removed.
It's convenient to use this api with ListAdapter, which wraps AsyncListDiffer to calculate diff on every list update, so you'll get RecyclerView animations for free.
Related
I am using Android API Level 28 with android components.
I have an app that displays our data visually in a chart. I'd like to background load the data and begin charting it as it comes in. I also would like you to be able to view the data as a list.
Ideally, the data for the chart and the list would be the same object instances. After all, the network call to retrieve the data is expensive. Plus, depending on the filters selected, there could be a lot of data to be charted.
I cannot use a server-based chart building API; I must draw graphics from the data on the device.
I am able to download the data in the background and build the chart. I looked into PagedList et al for the list view and that works terrifically.
What I cannot figure out is how to share the data. In other words, if you view the chart first then the list, the list should not trigger a download again since the data is already there. Likewise if the user scrolls through the list then looks at the chart.
This is especially helpful for tablets where one side displays the list and the other is the chart (eg, tapping on an item in the list highlights the data point in the chart).
Any pointers would be greatly appreciated.
What I tend to do is to build a class that handles the downloading of the data, then hand it out to whoever wants it
like
class BananaProvider {
private var _bananaData: List<String>? = null
val bananas: List<String>
get() {
_bananaData?.let{ return _bananaData!!}
_bananaData = getBananaData()
return _bananaData!!
}
fun getBananaData(){
(...)
}
}
and perhaps have a function for refilling your babana's with fresh data if you want.
Then, just drop an instance of the BananaProvider in every fragment/function/whatever that needs it.
Alternatively, if you want to share the data between activities, you could serialize it and send it in an intent, or drop it in an SQLite database if you want to save it locally for later use.
What I wound up doing was:
Created a DataSource with the functions to fetch the data from the server. I'm using a PositionalDataSource.
Created a DataSourceFactory to deliver my DataSource
Created a LiveData using LivePagedListBuilder with my factory and a PagedList.Config (to bring in 100 records at a time, which seems optimal for my needs)
It's the LiveData that I share between the RecyclerView and the chart.
The object which controls the download for the chart keeps track of the current download position and size. If the chart winds up creating the shared LiveData, this will trigger the initial download and populate the underlying PagedList that LiveData is observing.
Each time data comes down, I update the download position and size and draw points for the data just received. If the data isn't complete, I found that I have to wait a few milliseconds before triggering the next download using the PagedList's loadAround function.
If the RecyclerView is the thing which is opened first by the user, its PagedListAdapter automatically brings the data down through the same DataSource and it fills the shared LiveData as the user scrolls.
When I switch back to the Chart, since the LiveData is already populated (all or partially), it can draw the points. If the data isn't complete yet, the PagedList will attempt to load the rest, drawing the points of the data coming down.
It's a pretty neat method. The key was keeping the LiveData around and reusing it and figuring out how to "manually" populate it when not using RecyclerView.
I have:
a table for my entity, obj.
a (model) class, Obj.
a Room Entity, ObjEntity.
a Room DAO, ObjDao.
and finally a RecyclerView for my model, Obj.
Within my Obj class, I've added an insert method - Obj#Post, which handles storing this Obj to the app's database (handles converting to ObjEntity, foreign keys for child entities / lists, etc.)
The first thought is to requery the obj table for all objs, before taking this ObjEntity[], mapping it to a Obj[] before updating the (cached) in memory version of all objs - and notifying the recycler view adapter of an update.
This obviously doesn't seem like the best approach, as it would only update on an insert, and not a delete etc. This also doesn't follow the pattern of separation of concerns.
What I ideally want is for the RecyclerView to dynamically query the database itself, and update accordingly. However, the implementation of the adapter works based off of an ordered list structure. Now, the objs won't always have nicely contiguous ids - so unless I did some funky hack to map from the value returned by RecyclerView.Adapter#getItemCount to each ID , this doesn't seem like it would work. Also, most importantly, there's no notifying of changes to the adapter with this solution. It'd just be querying whenever it usually does - I don't know the implementation, I'm guessing some form of polling, or user interaction trigger.
Does Room have some form of callback API, i.e. ObjDao#onTableUpdated?
What you need is exactly designed in Android architecture component.
The LiveData notifies the view whenever there is a change in the Room Database.
The ViewModel handle the data operation independent to UI, that is complete CURD operation.
The Repository modules handle data operations. They provide a clean API so that the rest of the app can retrieve this data easily. They know where to get the data from and what API calls to make when data is updated. You can consider repositories to be mediators between different data sources, such as persistent models, web services, and caches.
App using these components
If you want to use it for pagination purpose in the recyclerview use PagedListAdapter instead of RecyclerView.Adapter
According to a response made by Yigit Boyar from Google, Live Data is not the best use case for a chat app, because it may lose displaying some items if they come at the same time. He recommends using the new Google's Paging Library. I've been using ItemKeyedDataSource for my inbox(all the people that have message the user), and the inside chat(the messages themselves). The problems are the following:
1- From the chat, when the user scrolls downwards, the user retrieves old messages, which means that the insertion of those messages should be in position 0 of the adapter, not sequentially like the paging library does. How can I alternate the position of the inserted items to be sequentially for new messages, and in position 0 for old messages?
2- From the inbox(people that have message the user), again I'm using ItemKeyedDataSource here, the problem is that I want to maintain the multiple document listener from the repository (I'm using Firebase Firestore), so I can detect every time a new person talks to the user. The problem is that callback.onResult is called only once, and fails when Firebase sends another user. How can I maintain an update-able list?
I understand that this answer is probably too late, but maybe it can help someone in future.
Position of item in RecyclerView is determined by the position of corresponding data object (of type T) inside PagedList<T>. PagedList is designed to look alike good old List<T>, but can be thought of as an "endless" list of elements.
PagedList gets its elements by pages on demand through something called DataSource.Factory. A Factory is used because DataSource by itself can only grow in one direction. If you need to prepend elements in PagedList, or change or remove existing elements you must invalidate the DataSource and a new instance will be created through DataSource.Factory.
So, to insert your data elements where you want them you should implement your own DataSource and DataSource.Factory by subclassing these base classes.
Note: Room, data persistence library from AndroidX, provides facilities to automatically generate instances of these classes for your data. You can write SQL query like this:
SELECT * FROM messages WHERE threadId=:threadId ORDER BY timestamp DESC
then get DataSource.Factory from this, use the factory to create LivaData<PagedList<Message>> and finally use the paged list to display messages in a RecyclerView in a chat application. Then, when you insert, update or remove data inside DB these changes will automatically propagate to the UI. This can be very useful.
I recommend you to read a few related examples a do codelabs:
https://codelabs.developers.google.com/codelabs/android-paging/#0
https://github.com/googlesamples/android-architecture-components/tree/master/PagingSample
https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample
I have a RecyclerView with elements updated via WebSocket. The update can affect several elements of the list in different places. I'd like to used DiffUtil to update elements of the RecycleView. The socket update itself doesn't contain the whole list element structure but just a few fields. So in order to update I need get the current data list from the adapter, look up for a elements that needs to be updated, update the fields and pass the new list into DiffUtils to compare with the current one. The problem is that when I update object it also automatically updates in a RecyclerView adapter because it's kept as a reference. So when I get the update from WebSocket I already don't have an "old" list to be compared with updated one.
Finally, I have found solution on Medium. Here it is https://android.jlelse.eu/rxjava-and-immutable-diffcallback-in-android-f4637078b03b
Assume we have an Activity with n TextViews that represent one line notes. These notes are stored somewhere (local database, network etc), and each time onResume() being called, the proper number of TextViews are drawn according to that stored data.
Now, lets say the user want to delete a note, what would be the best way the resolve the specific TextView, back to its storage entity?
At the moment, the only way I know is by using View.Tag, and having some manager to translate it to data entity, but it look rather messy.
Are there any other options?
In Android, the Adapter acts a bridge between the view and the data model. You could display the n TextViews in either a ListView or a GridView, and when the user adds or deletes a note, the local or server database is first updated. Upon completion of the web service call and/or the local database update, the new data is added to the underlying Adapter. The View is then refreshed by calling adapter.notifyDataSetChanged(). This would be the way to do it.
Approaches:
If updating the local SQLite database, you could consider using a
CursorAdpater
to hold the data for the View, as it directly maps the entries in
the local database to the View.
If making use of a ContentProvider, it is even possible to combine
a CursorAdapter with a
LoaderManager
and a
CursorLoader:
these plug into the Activity / Fragment life-cycle and monitor
the underlying ContentProvider for changes that are published
automatically to the View on a separate thread.
It is also possible to use a
Filter
in conjunction with the Adapter to define a dynamic mechanism that
sorts the data entries on-the-fly. The filtering is performed by the
Filter on a separate thread, as per a query entered by the user,
possibly in an
AutoCompleteTextView.
References:
See the Retrieving a List of
Contacts
tutorial. The example here retrieves a set of contacts from the
contacts ContentProvider based on a dynamic, alphabetical search by
the user. It makes use of CursorAdapter, CursorLoader and
LoaderManager to monitor and update the data, and it displays the
search results in a ListView.
See also the Android Realtime (Instant) Search with Filter Class example, which shows how a Filter is to be used.
Android AutoCompleteTextView with Custom Adapter filtering.
Android AutocompleteTextView using ArrayAdapter and Filter.