Android: how to use CursorAdapter? - android

I have a database, a ListView, and a CustomCursorAdapter that extends CursorAdapter. A menu button adds an item to the database. I want the ListView to update and show this change. Normally it doesn't show this new item until i go to the homescreen and reopen the application.
I did eventually get it to work by calling cursor.requery() or mCustomCursorAdapter.changeCursor(newCursor) whenever I added a new item, but when I set autoRequery to false in the CursorAdapter constructor, it worked just the same. Why does it update correctly when autoRequery is set to false?
Am I using CursorAdapter correctly? What is the standard way of keeping the list updated with the database? And what does autoRequery do?

The idiomatic and imho correct way to automatically update Cursors is to call Cursor#setNotificationUri when they are created and before they are handed off to whatever requested them. Then call ContentResolver#notifyChange when anything in that Cursor's Uri's namespace changes.
For example, suppose you were creating a simple mail application and you wanted to update when new mail arrived but also provide various views on the mail. I'd have some basic Uri's defined.
content://org.example/all_mail
content://org.example/labels
content://org.example/messages
Now, say I wanted to get a cursor that gave me all mail and be updated when new mail arrives:
Cursor c;
//code to get data
c.setNotificationUri(getContentResolver(), Uri.parse("content://org.example/all_mail");
Now new mail arrives so I notify:
//Do stuff to store in database
getContentResolver().notifyChange(Uri.parse("content://org.example/all_mail", null);
I should also notify all the Cursors that selected for labels this new message met
for(String label : message.getLabels() {
getContentResolver().notifyChange(Uri.parse("content://org.example/lables/" + label, null);
}
And also, maybe a cursor is viewing that one specific message so notify them as well:
getContentResolver().notifyChange(Uri.parse("content://org.example/messages/" + message.getMessageId(), null);
The getContentResolver() calls happen where the data is accessed. So if it's in a Service or ContentProvider that is where you setNotificationUri and notifyChange. You should not be doing that from where the data is accessed, e.g., an Activity.
AlarmProvider is a simple ContentProvider that uses this method to update Cursors.

I created next method for ListView updating:
/**
* Method of refreshing Cursor, Adapter and ListView after database
* changing
*/
public void refreshListView() {
databaseCursor = db.getReadableDatabase().query(
CurrentTableName,
null,
null,
null,
null,
null,
"title"+SortingOrder);
databaseListAdapter = new DomainAdapter(this,
android.R.layout.simple_list_item_2,
databaseCursor,
new String[] {"title", "description"},
new int[] { android.R.id.text1, android.R.id.text2 });
databaseListAdapter.notifyDataSetChanged();
DomainView.setAdapter(databaseListAdapter);
}
end calls it each time after some changing in database

Related

SimpleCursorAdaptor alternative for displaying SQLite records in ListView

SimpleCursorAdaptor is deprecated. I found out it's not going to be ideal for me because it uses the UI thread and I will be displaying a few hundred records. I want to use something that uses a background thread. After some research, I came across CursorLoader. I have been looking at tutorials but I'm finding it really tricky to implement. If there is a better alternative to CursorLoader, let me know. Please could someone help me out. The code I have so far. is:
public void getListFromDb(){
Cursor res = myDb.ViewAll();
startManagingCursor(res);
//Map cursor from db to viewFields
String[] fromFieldNames = new String[]{DatabaseHelper.COL_2, DatabaseHelper.COL_3, DatabaseHelper.COL_4, DatabaseHelper.COL_5};
int[] toViewIDS = new int[]{R.id.viewName, R.id.viewAddress, R.id.viewPostcode, R.id.viewType};
//Create adaptor to map items from DB to UI
SimpleCursorAdapter myCursorAdaptor = new SimpleCursorAdapter(this, R.id.item_layout, res, fromFieldNames, toViewIDS);
// Set adaptor for listView
ListView myList = (ListView) findViewById(R.id.listViewLocations);
myList.setAdapter(myCursorAdaptor);
}
Actually only one of the SimpleCursorAdapter constructors is depricated, not the entire class. You can still use it, but you must use the "standard" constructor which requires a value for the "flags" parameter.
public void getListFromDb(){
Cursor res = myDb.ViewAll();
startManagingCursor(res);
//Map cursor from db to viewFields
String[] fromFieldNames = new String[]{DatabaseHelper.COL_2, DatabaseHelper.COL_3, DatabaseHelper.COL_4, DatabaseHelper.COL_5};
int[] toViewIDS = new int[]{R.id.viewName, R.id.viewAddress, R.id.viewPostcode, R.id.viewType};
//Create adaptor to map items from DB to UI
// *** ADD THE FLAGS PARAMETER ***
SimpleCursorAdapter myCursorAdaptor = new SimpleCursorAdapter(this, R.id.item_layout, res, fromFieldNames, toViewIDS, FLAG_AUTO_REQUERY);
// Set adaptor for listView
ListView myList = (ListView) findViewById(R.id.listViewLocations);
myList.setAdapter(myCursorAdaptor);
}
I've added "FLAG_AUTO_REQUERY" to make the change obvious, but I suspect you might want to use the value "0" (no flags) unless your data can change while being displayed.
So after re-reading your question (sorry, my bad; I should really read more carefully :-p ) your real problem is how to load the data on a background thread. There are quite a few ways of doing this, all have good and bad points.
There are two parts to this classic Android problem:
1: Actually loading the data on a background thread and handing the results to the UI thread for display.
2: Surviving activity destruction/resume typically caused by rotation (but can be caused by something as uncontrollable as a phone call being received).
CursorLoaders are clumsy and awkward to implement but do actually solve both problems pretty well..
AsyncTasks will handle background execution and passing the results to the ui thread and work well if you don't care about problem 2, however there is a nasty little issue in android that can result in only one AsyncTask running at a time (not great if you want to do lots of work in parallel).
There are also other frameworks that can be used to do this kind of work like rxJava and otto but they require a considerable effort to become comfortable with initially (although arguably worthwhile).
If you need to worry about rotation and/or interruption of your application I would suggest CursorLoaders might be the easiest initially, but only for simpler applications. If your application is going to be complicated, invest some time in looking at rxJava or Otto.
Hope this helps.
P.S. Simple asynctask based example (haven't tested as I dont have all your source/resources, but you get the idea..)
public void getListFromDb(){
//Map cursor from db to viewFields
String[] fromFieldNames = new String[]{DatabaseHelper.COL_2, DatabaseHelper.COL_3, DatabaseHelper.COL_4, DatabaseHelper.COL_5};
int[] toViewIDS = new int[]{R.id.viewName, R.id.viewAddress, R.id.viewPostcode, R.id.viewType};
// Notice I am passing null as the cursor..
SimpleCursorAdapter myCursorAdaptor = new SimpleCursorAdapter(this, R.id.item_layout, null, fromFieldNames, toViewIDS, 0);
// Set adaptor for listView
ListView myList = (ListView) findViewById(R.id.listViewLocations);
myList.setAdapter(myCursorAdaptor);
new AsyncTask<SimpleCursorAdapter, Void, Cursor>() {
private SimpleCursorAdapter mSimpleCursorAdapter;
#Override
protected Cursor doInBackground(SimpleCursorAdapter... params) {
// Save cursorAdapter to use in postExecute
this.mSimpleCursorAdapter = params[0];
// Load cursor on background thread
return myDb.ViewAll();
}
#Override
protected void onPostExecute(Cursor cursor) {
super.onPostExecute(cursor);
// and update the cursor (which is already in the listview)
this.mSimpleCursorAdapter.changeCursor(cursor);
}
}.execute(myCursorAdaptor);
}
The main "trick" to it is that I initially create the simplecursoradapter with a null cursor; this allows me to get the list on the screen with no data. I then start an AsyncTask, passing the cursoradapter to it and get it to load the cursor on a background thread. I then call "changeCursor" on the adapter and it should update the listview with the loaded data. You could also use onPreExecute to show a busy "spinner" and hide the busy "spinner" in onPostExecute.. just to let the user know you are doing something.. Oh, just in case you missed it; onPreExecute and onPostExecute run on the UI thread so can update the UI, doInBackground runs on a background thread..

Way to update UI after database change has occured

I'm developing an app based on Google IO presentation architecture using the first approach. Basically I have a Service, ContentProvider backed by SQLite DB and I also use Loaders.
I need a way to update UI when changes to my database occur. For instance a user might want to add an item into his basket. After I insert the item id into the basket table I want to update the UI. What approach should I use? I've seen very little information on ContentObserver so far. Is it the way to go?
In the query method of your ContentProvider attach a listener to the returned cursor:
Cursor cursor = queryBuilder.query(dbConnection, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Then in your insert/update/delete methods use code like this:
final long objectId = dbConnection.insertOrThrow(ObjectTable.TABLE_NAME, null, values);
final Uri newObjectUri = ContentUris.withAppendedId(OBJECT_CONTENT_URI, objectId );
getContext().getContentResolver().notifyChange(newObjectUri , null);
Your CursorLoader will be notified and the OnLoadFinished(Loader, Cursor) will be called again.
If you're not using a Loader, the ContentObserver is the way to go, with a few lines of code you are notified on db changes (but you will need to requery manually).
private ContentObserver objectObserver = new ContentObserver(new Handler()) {
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
restartObjectLoader();
}
};
Remember to call in onResume():
getContentResolver().registerContentObserver(ObjectProvider.OBJECT_CONTENT_URI, false, objectObserver);
and in onPause():
getContentResolver().unregisterContentObserver(objectObserver);
Update: UI Changes
This is a larger topic because it depends on the Adapter you use to fill the ListView or RecyclerView.
CursorAdapter
In onLoadFinished(Loader loader, Cursor data)
mAdapter.swapCursor(data);
ArrayAdapter
In onLoadFinished(Loader loader, Cursor data)
Object[] objects = transformCursorToArray(data); //you need to write this method
mAdapter.setObjects(objects); //You need to wrie this method in your implementation on the adapter
mAdapter.notifyDataSetChange();
RecyclerView.Adapter
In onLoadFinished(Loader loader, Cursor data)
Object[] objects = transformCursorToArray(data); //you need to write this method
//Here you have more mAdapter.notify....()
Read from here for different way to notify the RecyclerView.Adapter.
If you are using a list, you can fill adapter again and set it to your list. Or try to inform data set change.

When to close Cursor used in SimpleCursorAdapter

I'm using a SimpleCursorAdapter to display results in a ListView but since I've got to query my database lots of times during a search (using the SearchView widget) it worries me that the cursor might be left opened.
This is how I query my database and show the results in a ListView:
class SearchCustomers extends AsyncTask<String,Void,Cursor>{
#Override
protected Cursor doInBackground(String... params) {
//get the query
String query=params[0].toLowerCase(Locale.getDefault());
Cursor cursor=mDB.searchCustomersByName((query != null ? query : "####"));
return cursor;
}
#Override
protected void onPostExecute(Cursor result) {
if (result != null) {
String[] from = new String[] { QuickOrderDB.ID,
QuickOrderDB.NAME,
QuickOrderDB.ADDRESS,
QuickOrderDB.PHONE_NUMBER };
int[] to = new int[] { R.id.customerIDTextView,
R.id.customerNameTextView,R.id.customerAddressTextView ,
R.id.customerPhoneTextView };
SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter(SearchCustomersActivity.this,
R.layout.results_customer_item, result, from, to);
mResultsListView.setAdapter(cursorAdapter);
}
}
}
I have tried many things to close the cursor, but even If I close it after mResultsListView.setAdapter(cursorAdapter); the result is always the same: an empty ListView.
I've already seen a couple of questions in which it is mentioned that the cursor will be closed automatically, but I want to make sure this is true.
Is there any official documentation about this? Does the SimpleCursorAdapter really close the cursor automatically??
Thanks in advance.
You need to close your cursor once you are done with it. Closing it after setAdapter() call would prevent the adapter from accessing the data. Hence a better place to close the cursor would be during current activities tear down life cycle stages such as onPause() or onStop(). (onDestroy() should not be used as Android run-time does not guarantee calling it. I think on latest version onStop() is guaranteed)
I don't think SimpleCursorAdapter adapter automatically closes the cursor automatically. The official document mentions that changeCursor() automatically closes the old cursor, so another option could be to change your cursor after search.
http://developer.android.com/reference/android/widget/CursorAdapter.html#changeCursor(android.database.Cursor)
It's better if you get the Cursor using a CursorLoader instead of an AsyncTask.
The Loaders are synched to the Activity/Fragment lifecycle via the LoaderManager, and the system will close the Cursor provided by the CursorLoader automatically for you when it's needed.
You should close the cursor in your fragment or activity's onPause() callback. After the activity is paused it's possible that older Android systems will delete the app to free memory.
This implies that you need to reestablish the cursor in the corresponding onResume() callback.
Don't create a variable for the cursor, just add the db query directly into the constructor as the argument c, db.query() or a method that holds the desired query), this seems to work.
SimpleCursorAdapter (Context context,
int layout,
Cursor c,
String[] from,
int[] to,
int flags)

When to close cursor in Android?

I have an app that uses a cursor to select data via rawQuery from an SQLite DB to populate a ListView in Android. Every time the user clicks on a listview item I create a new instance of Activity to re-populate listview.
Is it better to call cursor.close() and db.close() to avoid memory problems? I actually have db.close() in OnDestroy() of my activity.
You can close the cursor once you have retrieved the values for that particular object inside your method.
btw...You don't have to recreate a listview every time for a user click event. Just notify that there is some change in data of your adapter that has been set on the listview.
Something like
youradaptername.notifyDataSetChanged();
This should repopulate contents inside ur listview automatically.
Well if you are creating a new instance every time of the same Activity (though I am not sure its a good programming practice). You can close the cursor as soon as your have finished traversing / iterating through the source of the listview.
Example:
A sample implementation would be something like
//Pre cursor code
startManagingCursor(cursor);
if (cursor.moveToFirst()) {
do {
if (cursor.getString(0).equals(value)) {
cursor.close();
a = true;
return a;
}
} while (cursor.moveToNext());
}
//Close cursor here, when its work is complete
cursor.close();
//Post cursor code ...

Android SimpleCursorAdapter doesn't update when database changes

I have an Android ListActivity that is backed by a database Cursor through a SimpleCursorAdapter.
When the items are clicked, a flag field in the coresponding row in the database is toggled and the view in the list needs to be updated.
The problem is, when the view that's updated goes off screen and is recycled, the old value is displayed on the view when it returns into view. The same thing happens whenever thr list is redrawb (orientation changes, etc).
I use notifydatasetchanged() to refresh the cursor adapter but it seems ineffective.
How should I be updating the database so the cursor is updated as well?
Call requery() on the Cursor when you change data in the database that you want reflected in that Cursor (or things the Cursor populates, like a ListView via a CursorAdapter).
A Cursor is akin to an ODBC client-side cursor -- it holds all of the data represented by the query result. Hence, just because you change the data in the database, the Cursor will not know about those changes unless you refresh it via requery().
UPDATE: This whole question and set of answers should be deleted due to old age, but that's apparently impossible. Anyone seeking Android answers should bear in mind that the Android is a swiftly-moving target, and answers from 2009 are typically worse than are newer answers.
The current solution is to obtain a fresh Cursor and use either changeCursor() or swapCursor() on the CursorAdapter to affect a data change.
requery is now deprecated. from the documentation:
This method is deprecated.
Don't use this. Just request a new cursor, so you can do this asynchronously and update your list view once the new cursor comes back.
after obtaining a new cursor one can use theadapter.changeCursor(cursor). this should update the view.
In case of using loader and automagically generated cursor you can call:
getLoaderManager().restartLoader(0, null, this);
in your activity, just after changing something on a DB, to regenerate new cursor.
Don't forget to also have event handlers defined:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader =
new CursorLoader(this,
YOUR_URI,
YOUR_PROJECTION, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
I am not clear if you set the autoRequery property of CursorAdapter to true.
The adapter will check the autoRequery property; if it is false, then the cursor will not be changed.
requery() is already deprecated, just implement the simple updateUI() method like this in your CursorAdapter's child class and call it after data updates:
private void updateUI(){
swapCursor(dbHelper.getCursor());
notifyDataSetChanged();
}
It's easy.
private Db mDbAdapter;
private Cursor mCursor;
private SimpleCursorAdapter mCursorAd;
.....................................
//After removing the item from the DB, use this
.....................................
mCursor = mDbAdapter.getAllItems();
mCursorAd.swapCursor(mCursor);
Or use CursorLoader...

Categories

Resources