Cursor#getCount is used to ensure content window is filled? - android

Below is a code snippet from an Android tutorial book I was following. loadInBackground gets a cursor and then does cursor.getCount() to "ensure that the content window is filled". What does this mean? The docs on getCount just say "returns number of rows in the cursor". I've Googled for this "ensure that the content window is filled" and there are quite a few snippets that do this, all with the same comment, but no explanation of why this is needed/how it works.
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.database.Cursor;
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor cursor;
public SQLiteCursorLoader(Context context) {
super(context);
}
protected abstract Cursor loadCursor();
#Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
cursor.getCount(); // ensure that the content window is filled
}
return cursor;
}
}

As you know, Database returns a Cursor after a query. However, Cursor is only effectively filled with data when you try to read some information like: cursor.getCount() or cursor.moveToFirst() etc...
This is more evident during large queries.
For example, imagine that query below would return thousand of results:
Cursor cursor = db.rawQuery("select * from TABLE", null);
That statement however, does not take too much time to run...
However, when you finally call cursor.getCount() or cursor.moveToFirst(), for the first time, you may see some "lag" since the cursor is being effectively being filled with the data from database.
If you do that in Main UI Thread, you app may freeze for some seconds. Specially on low tier devices.
So, by calling that method, I believe that author is trying to ensure that data was fully loaded during loadInBackground(). This way, he ensure that data is loaded in Background an not in any other future method. This way, any future call to getCount() or moveToFirst() will be executed very quickly since the data was already loaded.
Anyway, it is not mandatory..

Related

How does a cursor used by a RemoteViewService get deactivated

I get ACRA exception reports from some users that the cursor which supplies data to my appwidget (RemoteViewService) is deactivated/closed. It never happens to me in person, but it happens enough where it's a bit of an issue.
Here's the code to my RemoteViewService:
public static class ListItemService extends RemoteViewsService {
public RemoteViewsFactory onGetViewFactory(final Intent intent) {
return new RemoteViewsFactory() {
private MyCursor cursor;
public void onCreate() {
// Nothing
}
public synchronized void onDestroy() {
if (this.cursor != null)
this.cursor.close();
}
public synchronized RemoteViews getViewAt(int position) {
// Here I read from the cursor and it crashes with
// the stack trace below
}
public int getCount() {
return ((this.cursor != null) ? this.cursor.getCount() : 0);
}
public int getViewTypeCount() {
return 1;
}
public boolean hasStableIds() {
return true;
}
public long getItemId(int position) {
return position;
}
public RemoteViews getLoadingView() {
return null;
}
public synchronized void onDataSetChanged()
{
if (this.cursor != null)
this.cursor.close();
this.cursor = getApplicationCntext().getContentResolver().query(myUri, null, null, null, null);
}
};
The stack trace varies from platform version to platform version. For example, I get the following on 4.0.3:
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
On 2.3, I get a:
java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery
I cannot, for the life of me, figure out who or what closes the cursor on me, other than from onDestroy() and onDataSetChanged(). Some users had reported that they weren't actively working with app when the crash happened.
I suspected maybe multiple calls to the ContentProvider return the same cursor and when I display my UI which uses the same query, they step on each other. But this doesn't appear to be the case as the cursor objects are different.
Any ideas?
Looks to me like a sync issue between multiple threads, one closing the previous cursor and one immediately accessing it thereafter.
You may want to consider closing the cursor right when you first can, such as a onPause event.
Also - you can add a safety precaution as checking cursor.isClosed() before you access it again. You can also add some synchronisation to your code.
A helper method that fetches the new cursor with a local var and only once it's done, replaces the previous one and closes it may be a quicker solution in the meantime.
From AOSP doc;
This interface provides random read-write access to the result set
returned by a database query. Cursor implementations are not required
to be synchronized so code using a Cursor from multiple threads should
perform its own synchronization when using the Cursor.
From Content provider basics,
The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query's projection for the rows that match the query's selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursor implementations automatically update the object when the provider's data changes, or trigger methods in an observer object when the Cursor changes, or both.
Try to add this.cursor = null on onDestory() method
public synchronized void onDestroy() {
if (this.cursor != null){
this.cursor.close();
this.cursor = null
}
}

What's the mechanism of setNotificationUri?

I've just implemented a CursorLoader and it works great! In fact, I didn't believe that my ListView would automatically update when the underlying data changed until I tested it. This apparently is the magic of setNotificationUri.
My question is, how does it know when the data in the cursor has changed? Say I quietly insert an additional row somewhere. Does the underlying mechanism constantly query the database and compare it with the past data? Won't that be horribly inefficient if the datasets are large?
Before I used cursorloaders, I would manually refresh when necessary. It's great that I don't have to do this anymore, but is it efficient to let the CursorLoader to this in the background?
Please, correct me if I'm wrong somewhere.
ContentProvider calls something like this in query(…) method:
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
CursorLoader get cursor back and registers an observer.
/* Runs on a worker thread */
#Override
public Cursor loadInBackground() {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection,
mSelection, mSelectionArgs, mSortOrder);
if (cursor != null) {
// Ensure the cursor window is filled
cursor.getCount();
registerContentObserver(cursor, mObserver);
}
return cursor;
}
/**
* Registers an observer to get notifications from the content provider
* when the cursor needs to be refreshed.
*/
void registerContentObserver(Cursor cursor, ContentObserver observer) {
cursor.registerContentObserver(mObserver);
}
When someone modifies data, ContentProvider notifies ContentResolver about changes:
getContext().getContentResolver().notifyChange(uri, null);
ContentResolver in its turn notifies all registered observers.
Observer, registered by CursorLoader, forces it to load new data.

Is Android Cursor.moveToNext() Documentation Correct?

boolean android.database.Cursor.moveToNext() documentation says:
http://developer.android.com/reference/android/database/Cursor.html#moveToNext%28%29
Move the cursor to the next row.
This method will return false if the cursor is already past the last entry in the result set.
However, my book says to do the following to extract data from a cursor:
Cursor myCursor = myDatabase.query(...);
if (myCursor.moveToFirst()) {
do {
int value = myCursor.getInt(VALUE_COL);
// use value
} while (myCursor.moveToNext());
}
Who's right? These both can't be true. If you can't see the contradiction, imagine myCursor has 1 row returned from the query. The first call to getInt() will work, but then moveToNext() will return true because it is not "already" past the last entry in the result set. So now the cursor will be past the last entry and the second call to getInt() will do something undefined.
I suspect the documentation is wrong and should instead read:
This method will return false if the cursor is "already at" the last entry in the result set.
Must the cursor be already PAST (not AT) the last entry before the moveToNext() method returns false?
No Snark Please
A simpler idiom is:
Cursor cursor = db.query(...);
while (cursor.moveToNext()) {
// use cursor
}
This works because the initial cursor position is -1, see the Cursor.getPosition() docs.
You can also find usages of cursors in the Android source code itself with this Google Code Search query. Cursor semantics are the same in SQLite database and content providers.
References: this question.
Verbatim from the API:
Returns:
whether the move succeeded.
So, it means that:
Cursor in first row -> moveToNext() -> cursor in second row -> there's no second row -> return false
If you want the details, go to the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/database/AbstractCursor.java#AbstractCursor.moveToNext%28%29
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
I think I tend to stay away from solutions that are based on a hidden assumption like: I hope that sqlite never changes it api and that a cursor will always start at the first item.
Also, I have almost always been able to replace a while statement with a for statement. So my solution, shows what I expect the cursor to start at, and avoids using a while statement:
for( boolean haveRow = c.moveToFirst(); haveRow; haveRow = c.moveToNext() ) {
...
}
why is showing that a cursor needs to start at the first row, well 6 months down the line you might be debugging your own code, and will wonder why you didn't make that explicit so you could easily debug it.
It appears to be down to the Android implementation of AbstractCursor and it remains broken in Jellybean.
I implemented the following unit test to demonstrate the problem to myself using a MatrixCursor:
#Test
public void testCursor() {
MatrixCursor cursor = new MatrixCursor(new String[] { "id" });
for (String s : new String[] { "1", "2", "3" }) {
cursor.addRow(new String[] { s });
}
cursor.moveToPosition(0);
assertThat(cursor.moveToPrevious(), is(true));
cursor.moveToPosition(cursor.getCount()-1);
assertThat(cursor.moveToNext(), is(true));
assertThat(cursor.moveToPosition(c.getCount()), is(true));
assertThat(cursor.moveToPosition(-1), is(true));
}
All assertions fail, contrary to the documentation for moveToNext, moveToPrevious and moveToPosition.
Reading the code at API 16 for AbstractCursor.moveToPosition(int position) it appears to be intentional behaviour, ie the methods explicitly return false in these cases, contrary to the documentation.
As a side note, since the Android code sat on existing devices in the wild cannot be changed, I have taken the approach of writing my code to match the behaviour of the existing Android implementation, not the documentation. ie. When implementing my own Cursors / CursorWrappers, I override the methods and write my own javadoc describing the departure from the existing documentation. This way, my Cursors / CursorWrappers remain interchangeable with existing Android cursors without breaking run-time behaviour.
Cursor.moveToNext() returning a boolean is only useful if it will not move the cursor past the last entry in the data set. Thus, I have submitted a bug report on the documentation's issue tracker.
https://issuetracker.google.com/issues/69259484
It reccomends the following sentence:
"This method will return false if the current (at time of execution) entry is the last entry in the set, and there is no next entry to be had."

Android: Filtering a SimpleCursorAdapter ListView

Right now, I'm running into issues trying to implement a FilterQueryProvider in my custom SimpleCursorAdapter, since I'm unsure of what to do in the FilterQueryProvider's runQuery function.
In other words, since the query that comprises my ListView basically gets the rowID, name, and a third column from my databases's table, I want to be able to filter the cursor based on the partial value of the name column.
However, I am uncertain of whether I can do this directly from runQuery without expanding my DB class since I want to filter the existing cursor, or will I have to create a new query function in my DB class that partially searches my name column, and if so, how would I go about creating the query statement while using the CharSequence constraint argument in runQuery?
I am also concerned about the performance issues associated with trying to run multiple queries based on partial text since the DB table in question has about 1300-1400 rows. In other words, would I run into a bottleneck trying to filter the cursor?
You need to run a query that will return a new filtered cursor:
public class MyActivity extends ListActivity implements FilterQueryProvider {
private Cursor cursor;
#Override
public Cursor runQuery(CharSequence constraint) {
if(cursor != null) {
cursor.close();
}
cursor = somehowGetAFilteredCursorFor(constraint);
startManagingCursor(cursor);
return cursor;
}
}

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