Updating ListViews with a CursorLoader was a simple way of displaying datas from a DB into UI. Model modifications where propagated to the UI with no extra work, and maybe not so efficiently.
RecyclerView.adapter, gives access to more granularity, allowing for instance to specify the adapter that a particular item was removed.
But, what is the best place to call those preferred methods (notifyItem*), replacing notifyDataSetChanged?
Obviously, the adapter must not observe the contentProvider, otherwise it won't know the nature of the model modification (just as before).
Different patterns could be used, like adding a bus to publish modifications from the provider, creating a singleton model which would hold a reference to the adapter, maybe using presenters (introduced in L), or creating an activity-bound service.
Here is a common use case: a sync process inserts an entry in DB (or a gcm notification is received, which also inserts an entry in DB), then i want UI (if launched) to be updated via a call to notifyItemInserted. Where to place this call?
Thanks.
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.
I have a List of items that I want in a ListView, and I can make it work with setting a custom adapter every time the List grows, but the program flow is kind of weird and I have problems with persistence. (If I switch tabs, the UI gets rebuilt with an empty ListView.)
Now, in my day job I'm a C# developer, so when I look at this problem I see a WPF ListView bound to an ObservableCollection. Does Android/Java have something like that, a "fire and forget" connection between a UI element and a data structure?
You don't need to replace the adapter every time you change the data. The adapter "adapts" between data and view. There is no need to change the adapter as long as the way it adapts does not change.
Activity / Fragment lifecycle is not necessarily the lifecycle of your data collection. You can for example make a singleton data collection somewhere and use an adapter to display that collection all the time. Call .notifyDataSetChanged() on the adapter if you changed the data.
A persistent data collection in Android is probably best backed by a database. Take a look at LoaderManager & ContentProvider to provide and load data then displayed via CursorAdapter.
There is no automatic way of keeping a bunch of data available outside of your Activity / Fragment / .. lifecycle and it can get quite complicated but that's basically what you have to do if you want to keep data for longer than a given lifecycle. Singletons, Activity#onSaveInstanceState(), Activity#getLastNonConfigurationInstance(), Fragment#setRetainInstance(), ... are useful utilities to keep data in memory, databases are good for persistent data.
You have to do a little bit work yourself but it's possible. Use a ContentProvider as your DataSource. How the data is stored is up to you. I would prefer a SQLite-DB. A content provider has the possibility to add ContentObservers. (See this related question.)
You can write a CourserAdapter to fetch the Data from your content provider. And your ContentObserver should call notifyDataSetChanged() on your adapter. This closes the circle and your UI refreshes itself.
In Addition to zapls answer:
You can also write an adapter which contains a BroadcastReceiver. When your DataSource changes you can send a LocalBroadcast. The broadcast handler just calls notifyDataSetChanged() of your adapter. I think this would work around most of the lifecycle problems because only active elements will get the broadcast.
The google documentation has an example for such a solution.
I have a ListView that is displaying data from a large dataset. The data is retrieved from the web and put into a List<Data> (i.e. in memory) by a background task. (Think email inbox with polling updates.)
My problem is how to pass that data to the ListFragment/ListView/ListAdapter to display.
This List isn't permanent (so not in a Content Provider) but lives longer than activity (it's updated in the background when the activity isn't alive yet).
The only options appears to be:
Pass it via an Intent/Bundle. This requires serialization on some level which will be very expensive for my long list, especially as each time the List updated in the background, I have to set a new List which means the entire list gets re-serialized.
Create a Singleton that provides the list from anywhere in the program, and access SingletonListProvider.getInstance().getList() from my ListAdaptor. I don't like singletons and I'm worried about concurrency.
Use a ContentProvider. Seems overkill for a simple List<> that currently lives in memory
Are there any other options?
It seems I have little control over how the Activity (ListFragment in this case) is created so I can't just pass the List as a constructor parameter.
Best way is to use Database, especially if your data-structure large.
By the way, you shouldn't create ContentProvider enough to extend SQLiteOpenHelper.
http://www.vogella.com/articles/AndroidSQLite/article.html#sqliteoverview_sqliteopenhelper
Seems the Google-recommended way (using the android support library) for a fragment or activity to interact with a sqlite database is via a loader that extends AsyncTaskLoader and works similar to the way CursorLoader does for ContentProviders.
A single activity or fragment may interact with a database in a variety of ways, updating, deleting, and querying data in one or more tables. But since a loader has only one place in which to carry out database manipulation (i.e. in loadInBackground()), is the developer expected to write a separate loader for each type of interaction, especially since it seems the only way to parametrize the behavior in loadInBackground() is by passing arguments to the loader's constructor via the args argument to LoaderManager.initLoader()?
Thanks much.
When not using a ContentProvider, i.e. when using sqlite directly, use AsyncTaskLoader if the set of database operations to be performed returns a Cursor. The set may include any kind of operation - update, query, delete, insert. These operations should be executed in AsyncTaskLoader.loadInBackground().
If the set of database operations does not result in a Cursor being returned, use an AsyncTask. In this case, the operations should be executed in AsyncTask.doInBackground().
There are two key benefits to using a CursorLoader in your app over Activity.managedQuery():
The query is handled on a background thread for you (courtesy of being build on AsyncTaskLoader) so large data queries do not block the UI. This is something the docs recommended you do for yourself when using a plain Cursor, but now it's done under the hood.
CursorLoader is auto-updating. In addition to performing the initial query, the CursorLoader registers a ContentObserver with the dataset you requested and calls forceLoad() on itself when the data set changes. This results in you getting async callbacks anytime the data changes in order to update the view.
Each Loader instance is also handled through the singular LoaderManager, so you still don't have to manage the cursor directly, and now the connection can persist even beyond a single Activity. LoaderManager.initLoader() and LoaderManager.restartLoader() allow you to reconnect with an existing Loader already set up for your query and, in some cases, instantly get the latest data if it is available.
Your Activity or Fragment will likely now implement the LoaderManager.Callback interface. Calling initLoader() will result in the onCreateLoader() method where you will construct the query and a new CursorLoader instance, if necessary. The onLoadFinished() method will be fired each time new data is available, and will include the latest Cursor for you to attach to the view or otherwise iterate through.
In addition, there is a pretty good example of all this fitting together on the LoaderManager class documentation page: http://developer.android.com/reference/android/app/LoaderManager.html
Hope that Helps!
Based on the previous answers, I'm not sure if this is what you're asking exactly. But YES, you have to make a loader with its unique id for each data set i.e. each type of query you perform.
I have a ListView with a custom ArrayAdapter, that shows orders from an ArrayList. These orders are supposed to be synced with my server. There I have an API, which I am requesting for new orders, and if there are any, I get an XML of the order back. What is the best approach to dynamically update the listView? I do not want to use SQLite to store the orders, because only last ten are supposed to be displayed. With a ContentProvider I am not able to store my custom Order object. And if I wrap the ArrayList into a singleton class and use it in the service as well as in the Activity class for the ArrayAdapter, the ListView is not dynamically updated (probably, because the arrayAdapter makes a copy of the arraylist?). Thank you very much.
Filip
use Intent or Bundle
i'm no sure what you mean regarding the ArrayAdapter not being updated, but i can give you a solution we used in my company.
I have a DataMaanger which is a bridge between the Activities and the Networking or SQLite.
The dataMaanger keeps it's data in memory so it's not in DB or on disk. the disadvantage of it is if your app gets killed for lack of memory and reconstructs itself, the dataManager will be empty, which leaves you with two options, either on every Activitie's death or you main task's activities death you serialize your DataManager's data, or if you are not dependant on any previous data, just make arequest again and update the data manager.
I use broadcasts to notify my activities.
To get an access to the DataManager i don't use a sigletone. i use the Application object, you can extend it and in the Manifest.xml give it's name in the tag, then it will be used instead of the regualr Application object.
You can access it later by using getApplication() method in Activity class.