I can use a managedQuery like this:
Activity a = (Activity) context;
cursor = a.managedQuery(uri, null, null, null, null);
And once I do, I have a cursor that I can step through however I want.
However, using a CursorLoader, when a new CursorLoader is created the onCreateLoader call back method is called. The onCreateLoader call back returns a CursorLoader. How do I get a reference to the cursor so I can step though it, as in managedCursor.
I missing the boat here, appreciate any direction.
You need to also implement onLoadFinished, this method gives you the Cursor when the asynchronous load has finished
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
myadapter.swapCursor(cursor);
}
Related
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.
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)
I am using a rawQuery in my DB class and returning the cursor to my adapter which is a CursorAdapter which I use with a custom ListView item.
Is this cursor automatically closed after the view is painted in the screen or how to manage this cursor? what is the best practice in this scenario ?
If I close this cursor in DB class I am not able to access them from my adapter.
Thanks for your time and effort in helping me.
EDITing to add some code snippets for better understanding
This is my activity code:
calAdapter = new CalendarListAdapter(context, dbHelper.getAllCalendars());
drawerList.setAdapter(calAdapter);
This is my cursor
public Cursor getAllCalendars() {
String query = "SELECT calendarId as _id, calendarName, calState, calShow FROM "
+ TABLE_CALENDAR_LIST;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(query, null);
return cursor;
}
You can close the old cursor after you set the new one. swapCursor() returns the old Cursor, returns null if there was not a cursor set, also returns null if you try to swap the same instance of the previously set cursor. Knowing that, you can try something like this:
Cursor oldCursor = yourAdapter.swapCursor(newCursor);
if(oldCursor != null)
oldCursor.close();
Note that when you are using a Loader (LoaderManager.LoaderCallbacks), the framework is going to close the old cursor. This is what the documentation says:
onLoadFinished:
The loader will release the data once it knows the application is no
longer using it. For example, if the data is a cursor from a
CursorLoader, you should not call close() on it yourself. ...
Cursor does not get closed automatically. You need to close it. In older API you could register Cursor at Activity by calling Activity.startManagingCursor(). In this case Activity takes responsibility for managing it. In newer API you should better use LoaderManager.
I came across this info that not closing the cursor or database gobbles up memory. So as i use the cursor across a hundred test cases I might forget to close it somewhere. Can I declare a single cursor and reuse it again and again making different references and finally closing it in the OnDestroy() method.
Eg. Cursor a;
//Another function
a = as.rawQuery("select * from verse"+k, null);
//Another function
a = bs.rawQuery("select * from hello", null); //Another database
//Another function
a = cs.rawQuery("select * from chapter", null); //Another database
//OnDestroy()
a.close();
Is it a feasible solution??
You cannot use a single cursor; each rawQuery call creates a new cursor object.
When you assign a reference to the new cursor object to a, the old object is not closed (and you cannot anymore reach it).
To ensure that close is always called, use try/finally:
Cursor c = db.rawQuery(...);
try {
... read data ...
} finally {
c.close();
}
If you have a helper function that returns a cursor, do this in the calling function.
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...