I have a fragment that has a listview.
In onPause() I'm saving off the Y scroll position of the listview in a ContentProvider.
The same fragment upon onResume or onActivityCreated uses a loader to grab that y scroll position from the contentprovider and restore the scroll position.
If I exit the activity/fragment and return to it, this works, the listview returns to its last opened location since it was saved off to the contentprovider in onPause. So the code is 100% fine.
What isn't fine, is the data on a rotate. onPause saves fine, but the load after onCreateActivity results in retrieving old data, the data prior to the save in onPause. It results in the listview returning to the OLD position when they first opened the app and not the position where the listview was prior to the rotation.
It seems like an obvious race condition that the save to the content provider during onPause is not completed in the onPause, resulting in old data being loaded after the rotate.
So a rotate on my phone looks like this
01:31:33.026: ThreadViewerFragment.java(235):onPause Saved(position:Yposition): 59:-74
01:31:33.256: ThreadViewerFragment.java(194):onActivityCreated
01:31:33.266: ThreadViewerFragment.java(309):onLoadFinished Load(position:Yposition): 62:-149 //initial load is of old values
01:31:33.266: ThreadViewerFragment.java(309):onLoadFinished Load(position:Yposition): 62:-149
01:31:33.596: ThreadViewerFragment.java(309):onLoadFinished Load(position:Yposition): 59:-74 //this is loaded due to notification by the save in onPause which is supposed to be finished before recreating the new fragment???
So the order looks fine (doesn't look like a race condition in terms of activity/fragment flow) but clearly, it's loaded old values instead of the just saved values of 59:-74.
I'm not after a work around, I know how to use saveInstanceState etc. But why should I double up my code, is there a way to force the contentprovider to behave atomically (which I thought it already was?)
Edit: adding code and refined the question a bit better because I'm still not satisfied that we are any closer to understanding if contentprovider calls block while being executed and/or if those calls are atomic or if its just a misunderstanding of contentproviders and loaders.
In onPause, I'm saving off the Y position of the product listing
@Override
public void onPause() {
super.onPause();
Utils.logv("onPause Saved position: " + mLastRead + ", " + mYPos);
ContentValues contentValues = new ContentValues();
contentValues.put(ProductsContract.Products.Y_POS, mYpos);
int updateCount = getActivity().getContentResolver().update(
Uri.parse(ProductsContract.Products.CONTENT_URI + "/" + mId),
contentValues, null, null);
}
My understand is that the call to update should be a blocking call, and it occurs before the fragment is destroyed and before the new fragment is created to handle the rotation
In on resume I fire up my loader
public void onResume() {
super.onResume();
getLoaderManager().initLoader(PRODUCTS_LOADER_ID, null, this);
}
And in my loader I get the cursor, which reliably has old data after a rotate, but is fine in any other circumstance
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if(cursor.moveToFirst()){
mYpos = cursor.getInt(cursor.getColumnIndex(ProductsContract.Products.Y_POS));
Utils.logv("loader: " + mYpos);
}
}
So restating, after a rotate, the loader will consistently deliver old data.
I'm thinking perhaps it's the loader that is stale and not the content provider itself? That the cursor is saved and restored after the rotate even though it's stale?
It seems like an obvious race condition that the save to the content provider is not completed in the onPause before it is loaded after the rotate.
By assuming above statement is correct:
Usually content provider is fast and it is not suppose to take this much time. But, When ContetnProvider
backed by SQLite
, it is understandable that it will take time to insert data into database. Where as delay time will depend on amount of data and device hardware.
But, the problem with this approach is when, user just rotate the screen and really do not like to wait, to see data are being loaded one by one. Rotation should be fast very fast.
Alternative on this path is,
onPause
to complete the task(this will block the UI being created next time and very very bad idea)Further alternative on this path is, create a in-memory cache only for handling rotation case. That means every time you look for data first you try to find it inside cache, if not, try to load from ContentProvider
. You can use the same Content URI for the key of the cache. And, for writing, write first on the cache then in provider.
ContentProvider
provide some great benefits, no doubt on that. If you need ContentProvider
you should use it. But, for handling rotation you can think of other alternative. And, Way around solution would be, dumping things on a file and read that back. SharedPreferences
or typical File IO right now on top of my head. I guess their is a good reason these exist.
Finally, just out of curiosity, have you tried by loading data onResume
state of the fragment? And, hopefully setRetainInstance(boolean)
is not set, somewhere in your code.
Edit,
AFAIK, ContentProvider do not provider any atomicity or thread safety. From Android doc,
the methods query(), insert(), delete(), update(), and getType()—are called
from a pool of threads in the content provider's process, not the UI thread
for the process. Because these methods might be called from any number of
threads at the same time, they too must be implemented to be thread-safe.
Please read this and this
Probably i will be able to give you a better answer if i can see your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With