With ListView we have had a good native pattern to map some data from db to list:
DB -> ContentProvider -> CursorLoader -> CursorAdapter -> ListView
This approach was good in terms of data layer separation, performance and automatic data updates. But this pattern doesn't really fit new RecyclerView. There are some approaches to mimic old behavior:
Using the recyclerview with a database
But there is a problem with using old style notifyDataSetChanged with RecyclerView. It can't use ItemAnimator features, it loses scrolling position, and it's just ineffective.
So, how we can benefit from finegraned change notifications while using DB wrapped in ContentProvider? Cursor is static, and to get new data from it we need to get new Cursor. So, it seems that we will need an custom intermediate data layer, which will merge data from Cursors and expose the List of entities to RecyclerView.Adapter. Also, we will have to manually map ContentObserver onChange() events to RecyclerView notifications. This also means that we will have to get rid of CursorLoader. That is an incredible amount of work for such basic task.
Is there any better solution?
You can use the DiffUtils class to compute the differences between the old and new cursor.
When using it you just need to implement two methods :
areItemsTheSame() to know if two items represent the same logical item (even if the content is different). Usually you would base the answer on an identifying field of your item;
areContentsTheSame() to know if two items representing the same logical item have unmodified content.
Once the differences are computed, you can then just apply it to your adapter and it will automatically call the notifyItemChanged(), notifyItemInserted() or notifyItemRemoved() accodingly.
Related
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've heard from my friend that CursorAdapter does not follow the MVC rule, it take the values directly from Database to the View, not through the Model. Furthermore, he said everytime user iterate items via list view, CursorAdapter execute dbs query again and again. As a result, CursorAdapter will not be used frequently.
I'm very curious about this statement, anyone can help me out? Is he right or wrong? And if he is right, what Adapter I can use instead?
CursorAdapter... take the values directly from Database to the View, not through the Model
That depends on what you define your model to be. The Cursor could be the model, for trivial apps, in which case CursorAdapter takes data from the model and applies it to the view.
Your friend may be thinking of a model defined as a set of custom Java classes; in that case, CursorAdapter would know nothing about those classes.
he said everytime user iterate items via list view, CursorAdapter execute dbs query again and again
Not really. CursorAdapter knows nothing about executing database queries.
The only scenario that I can think of that resembles what you friend describes is if your query has a large result set, over 1MB. In that case, the Cursor will not hold the entire result set, the way it normally does. Instead, it holds a portion of the results, and if the user scrolls past what the Cursor holds, the Cursor will trigger database I/O to fetch more results (and let go of some past ones, to minimize the overall amount of memory held by the Cursor).
As a result, CursorAdapter will not be used frequently
I would say that it is less frequently used than is ArrayAdapter, and both are falling out of favor in general, as more developers move to RecyclerView and RecyclerView.Adapter.
I think what your friend really is concerned about is using a Cursor as a model, as opposed to having a "real" model (and perhaps view-models) as part of an MVC/MVP/MVVM architecture. Certainly what I hear from larger projects indicates that a Cursor is mostly used for populating other model objects, rather than being used by a CursorAdapter or RecyclerView.Adapter directly. But, it really depends a lot on the app. Trivial apps do not need strict adherence to some specific GUI architecture, and the dividing line between "trivial apps" and "larger projects" is difficult to define.
And if he is right, what Adapter I can use instead?
If your friend wants model Java objects, typically you would use an ArrayAdapter or a BaseAdapter that knows how to get at the collection of model objects. Or, in the RecyclerView realm, you would use a RecyclerView.Adapter that knows about the structure of your collection of model objects.
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.
I have a following situation in my android app.
I have an app that fetches messages from inbox, sent items and drafts based on search keywords. I use to accomplish this by fetching cursors for each manually based on selection by user and then populating them in a custom data holder object. Filter those results based on given keywords and then manually render view with respective data.
Someone suggested that I should use a custom Cursor adapter to bind view and my cursor data. So I tried doing that. Now what I am doing is this:
Fetch individual cursors for inbox, sent items and drafts. Merge them into one using Merge cursor and then pass that back to my CursorAdapter implmentation.
Now where or how do I filter my cursor data based on keywords; because now binding will ensure that they are directly rendered to view on list. Also, some post fetching operation like fetching sender's contact pic and all will be something that I do not want to move to adapter. If I do all this processing in adapter; it'll be heavy and ugly.
How could I have designed it better such that it performs and the responsibilities are shared and distributed.
Any ideas will be helpful.
Using cursors and adapters does not work out well for the most part. Our experience as led down a different technique.
Your best bet is to "pump" the cursors in an AsyncTask into an ArrayList<data-holding object>, then you can do processing and then sort that list as necessary with Collections.sort() and the Comparator of your own construction, and then use ArrayAdapter to present the resulting list. This releases cursors ASAP and stays off the UI thread while doing it and you can sort however you feel like.
Remember always process in the background and avoid ANR!
We use this in all of our apps (14 on The Market) and it works like Butter.