I am using a custom content provider and a CursorLoader for displaying a list of elements in a fragment.
First the CursorLoader calls the ContentProvider's query() to get all elements stored in my Database. In my query() function I start a thread which does a WebService call to update the elements in my database. The thread is parsing the WebServices' response and calling my ContentProviders bulkInsert().
When my bulkInsert() is done, i call notifyChange().
Now here is whats happening: I see that after the notifyChange(), my ContentProvider's query() method is called again, which leads to a new WebService call and so on and so on.
Here my query() method in the ContentProvider:
...
// Database query
ticketCursor = mDb.query(TBL_TICKET_NAME, projection, selection, selectionArgs, null, null, orderBy);
// set Notification URI
ticketCursor.setNotificationUri(getContext().getContentResolver(), TICKET_CONTENT_URI);
// WebService call --> This starts a new Thread for the WebService call
asyncSoapQuery(where, uri);
return ticketCursor;
My bulkInsert method looks like this:
mDb.beginTransaction();
for (ContentValues value : values) {
mDb.insertWithOnConflict(table, null, value, SQLiteDatabase.CONFLICT_REPLACE);
}
mDb.setTransactionSuccessful();
getContext().getContentResolver().notifyChange(TICKET_CONTENT_URI, null);
mDb.endTransaction();
return values.length();
So, my problem is that this causes an endless loop of webservice calls. I thought, that the notifyChange() is not calling the content provider's query() method again. I only want to do the WS call inside my query() again if the user hits "refresh" on my UI...
What am I missing here?
Thanks in advance for your help!
I have the same issue but I'm using insert() instead of applyBatch() in the content provider. I manage to resolve my issue by changing
mDb.insertWithOnConflict(table, null, value, SQLiteDatabase.CONFLICT_REPLACE);
to
long rowId = mDb.insertWithOnConflict(table, null, value, SQLiteDatabase.CONFLICT_IGNORE);
My next step is to check if rowId is greater than zero. Only when it is greater than zero then execute
getContext().getContentResolver().notifyChange
So it is something like this:
if (rowId > 0) {
notifyChange(uri);
return uri;
} else {
return null;
}
Related
I'm planning to use Android SQLite for the first time.
Cursor c = db.rawQuery("some select command here", null);
// some jobs with the cursor..
c = db.rawQuery("another select command here", null);
// some jobs with the cursor..
c.close();
db.close();
c = null;
db = null;
As you can see, I'm trying to call rawQuery() method several times.
Do I have to close the cursor before I call rawQuery() method AGAIN?
Do I have to assign null to the variables after closing the cursor and database like above?
Do I have to close the cursor before I call rawQuery() method AGAIN?
Close the cursor whenever you are finished reading from it. This is to release resources that are opened by the cursor, so yes, you should close the first cursor before the second query.
Do I have to assign null to the variables after closing the cursor and database like above?
It depends on the scope of the variables. If your code looks like this...
class Foo {
void doSomething() {
SQLiteDatabase db = ...
Cursor c = db.rawQuery...
// other stuff
c.close();
db.close();
}
}
... then there's really no point nulling them out because they will go out of scope immediately when the method finishes execution. But your actual code might look different. If you have some reason for wanting to allow those objects to be garbage collected, then you can null out the variables.
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 need to insert a lot of rows in the database at a time and I'm seeking for the most efficient way to do it. I have seen code like this:
db.beginTransaction();
for (ModelObject object : modelObjectsCollection){
ContentValues values = new ContentValues();
... // fill values variable with values from object
db.insert(TABLE_NAME, null, values);
values.clear();
}
db.setTransactionSuccessful();
In this case all inserts are accomplished as a single operation which takes less time. Will it still work as a single operation if I incapsulate insertion of a single row into a method like this:
public void insertAllRows(){
db.beginTransaction();
for (ModelObject object : modelObjectsCollection){
insertSingleRow(object);
}
db.setTransactionSuccessful();
}
public void insertSingleRow(ModelObject object){
ContentValues values = new ContentValues();
... // fill values variable with values from object
db.insert(TABLE_NAME, null, values);
}
Will it be accomplished in a single transaction as well?
Besides, I do not understand: is it correct that if we do not call
db.beginTransaction(); ... db.setTransactionSuccessful();
explicitly, but only call db.insert(), beginTransaction()-setTransactionSuccessful() methods are invoked inside insert(). In contrary, if we invoke insert() between invokations of beginTransaction()-setTransactionSuccessful(), the latter 2 methods aren't invoked inside insert()?
You can execute queries without transactions at all. And there are no need in transaction for single query at all.
Also you need to call db.endTransaction(); after db.setTransactionSuccessful() for commiting it.
upd: you can find more about common transaction use here http://www.4js.com/online_documentation/fjs-fgl-manual-html/User/Transactions.html for example.
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 it ok to call the SQLiteDatabase update method in the insert() overridden method of a content provider?
Basically it's fine, but since you didn't provided code, I just can post 2 possible ways for it
First:
// In your content provider
public Uri insert(...) {
long insertId = db.insert(...);
if(insertId == -1) {
// insert failed, do update
db.update(...);
}
}
Second:
public Uri insert(...) {
long insertId = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE)
if(insertId == -1) {
// insert and update/replace failed
}
}
Check out SQLiteDatabase for reference on the forth parameter. In most cases the last method should be sufficient, unless you want only certain fields being updated if the row exists and have all fields if it doesn't.
Most useful need for insertWithOnConflict may be, that you could insert a row and if it already exists, ignore the insert and instead return the Uri/primary key of the already existing row.
It's your choice what you write in your overridden methods.
So yes, it is ok.
I don't know what you're trying to do, but you might want to to take a look on the SQLiteDatabase's replace() method too. Maybe it better suits your needs.