I have a CursorLoader that provides data to a ListFragment. I have the notification mechanism correctly configured in my ContentProvider with:
cursor.setNotificationUri(getContext().getContentResolver(), uri);
and
getContext().getContentResolver().notifyChange(uri, null)
When I select a list item, a new child Activity is opened and in the onCreate() of this Activity I do a query modifying the item details.
I know the notification is fired and the CursorLoader re-queries the content provider.
The problem I'm facing is that sometimes my CursorLoader is cancelled because the parent Activity is being paused (or stopped) and the onLoadFinished() never gets called.
When the CursorLoader is cancelled, even if I re-init the loader onResume() of the ListFragment, I can't see any change in the data:
#Override
public void onResume() {
super.onResume();
getLoaderManager().initLoader(URL_LOADER, null, this);
}
I'm using support library v4.
I'm assuming CursorLoader updates the parent Activity even when it is on background. Do I need to restart the loader when the ListFragment resumes?
(edit)
I forgot to mention that I'm creating and starting the loader from a ListFragment.
After some debugging, I found that when the ListFragment is stopped, the loader manager cancels all current loaders for the fragment.
In my case, when the new child activity is created, a db change triggers a notification that makes the loader perform a re-query. If the query does not finish before the parent fragment stops, the loader is canceled and the old cursor is still valid. When I navigate back to this parent activity with the ListFragment from the child activity, the data shown is from the old cursor. In this case, the notification is lost and I have to restart the loader onResume().
I did a test where I modified the db only after 10s since the child activity started, and because the loader was not canceled (because the parent activity was already stopped), I received a new cursor when returning to the parent activity.
Can anybody help me find a solution that does not involve a forced re-query (restartCursor()) every time the parent activity resumes?
When using the support library, you need to use getSupportLoaderManager() instead of getLoaderManager(), But you can only get it from the FragmentActivity. So try using this code instead:
getActivity().getSupportLoaderManager().initLoader(id, null, this);
Related
My AsyncTask for fetching data is a long one, and at the same time, in the midst of constructing my recyclerview I wanted to check if a cursorloader was able to query from my content provider correctly.
Bear with me here, I used Loader Callbacks interface and onLoadFinished to get results. Since the asynctask is still running, the loader still calls the content provider as it gets updated until asynctask ends. I would except that since onLoadFinished primarily deals with filling in the contents of an adapter with cursor data that I shouldn't worry that it prints out log statements simultaneously as the asynctask continues to run, but I wanted to confirm.
I do intend to eventually move this asynctask into an intentservice that only gets called via broadcast.
You can call getLoaderManager().restartLoader(URL_LOADER, null, HomeFragment.this); to restart the loader on you AsyncTask onPostExecute method. You need to init the Loader first on you onCreate() method.
This way, the loader will refresh with the same projection it had when you started it and it will fetch the results of the AsyncTask operations from DB.
From this developer guide on Loaders,
They automatically reconnect to the last loader's cursor when being
recreated after a configuration change. Thus, they don't need to
re-query their data.
I assume that by "last loader", they mean that the loader which was destroyed when the Activity was destroyed during the configuration change. Then by saying "last loader's cursor", they mean the cursor which was associated with the last loader. This tells us that when an Activity is destroyed because of a configuration change, its loaders are also destroyed, but the cursors (or any other form of data that the loader has loaded) is not destroyed. Is that correct?
Actually it is a little difficult to internalize this. My intuition says that when the loader is destroyed, everything associated with it, including the cursor associated with it should also be destroyed.
The Loader instance is kept alive during configuration change. The LoaderCallbacks of the old Activity is disconnected and the new one reconnected.
Take a look at LoaderManager.java around the retain() and finishRetain() methods of LoaderInfo. You can see that the mCallbacks field is nulled, but the mLoader field is not.
The initialization of these fields happens in LoaderInfo.start(), where you can see that mCallbacks.onCreateLoader() is only invoked if mLoader is null.
On a button click I want to create a new SQLite database from two XML files. Using LoaderManager and AsyncTaskLoader seems to be appropriate for this.
The Fragment.onCreate() calls:
setRetainInstance(true);
When the button is clicked, my Fragment sets a flag that it is creating the database, changes the layout to show a ProgressBar and cancel button, and calls:
getLoaderManager().initLoader(0, extra, this);
The operation takes 12 seconds on my PC/Emulator. If I rotate the screen, the loader continues to call the Fragment's onLoaderProgressUpdate(int) (an interface callback method) and completes the creation of the database. But it never calls the onLoadFinished() method, where I can reset the layout, enable buttons and the action bar menu that require the database to be there.
I tried doing this in Fragment.onCreateView():
if (mCreatDBInProgress) {
mBTN_CreateDB.setEnabled(false);
mDBLayout.setVisibility(View.GONE);
mProgressBarLayout.setVisibility(View.VISIBLE);
getLoaderManager().restartLoader(0, null, this);
}
But it calls my onCreateLoader() method and starts the whole thing from scratch. This is a problem, since I have not called the method that deletes the database and goes through the process of determining what XML files to use for creating the database. The XML filenames are what I pass to that method in the Bundle arg.
I don't get why the ProgressBar callback method continues to work, but the AsyncTaskLoader can't call the onLoadFinished() method. LogCat shows that it did the deliverResult().
I tried to make the Bundle a member variable, so I could re-use it on the restartLoader() call. It start another asyncTaskLoader and my ProgressBar goes back and forth as both threads try to update it. Eventually, it gets an SQLConstraintException trying to add a row to the database that was already added by the first thread.
Well, suck me sideways! I came up with one more idea, and it worked!
Instead of calling restartoader() after the rotation, just call:
getLoaderManager().initLoader(0, mExtra, this);
For (whatever) reason, that causes the AsynTaskLoader to just continue with the first thread (instead of starting another) and relink to my Fragment for the onLoadFinished() call.
So:
initLoader() - reconnects the loader to your Fragment IF you call it with the same arguments (int, Bundle, this).
I had the same problem, when after rotating screen onLoadFinished not called.
I've seen a lot of answers but nothing really fix the issue.
The only thing that helps is to simply add call to getLoaderManager() in onCreate method (it is called again after activity is recreated because of rotation).
It's really fixed the issue for me:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoaderManager loaderMngr = getLoaderManager();
Log.d(LOG_TAG, "" + loaderMngr);
And a little note about why I didn't just placed initLoader or restartLoader here: because Loader in my case is triggered after another event happens, so this was the only way to solve this problem for me.
Originally idea got from here.
In the click handler for a button, I'm loading some data from a content provider (using getContentResolver().query(...)), then sending that data off in a network request. Since the query happens on the main thread with this approach, I want to move this off the main UI thread.
I think I can use a LoaderManager, and fire off the network request in onLoadFinished(), but the problem is that I don't want onLoadFinished() called ever again (for that Loader id), because I don't want to fire the network request again, during a screen orientation for example.
So, how do I use a LoaderManager for a query that I only want to happen only once?
Calling LoaderManager#initLoader() in your Activity#onCreate() method will either create a new Loader and force a new load, or reuse an existing Loader and deliver the most recently queried data if any exists. So as long as you are using the LoaderManager correctly (i.e. the way the developer's site recommends in the documentation), you shouldn't have any problems.
In your onLoadFinished(), you can call getLoaderManager().destroyLoader(loaderId) (or getSupportLoaderManager() as applicable). That will stop the Loader from automatically reloading. You may also need to ensure that where you are calling initLoader is guarded from executing again (via saving a boolean variable in onSaveInstanceState for example).
Can somebody explain me the flow of control in this tutorial : http://www.vogella.de/articles/AndroidSQLite/article.html#tutorialusecp
I am not able to get the right flow. I am completely a novice to this content provider etc.
I wanted to know when does actually the DB gets created, what are the lifecycle methods and what is the sequence of method execution in this project ?
Finally found the Flow!! :
First of all, the onCreate of Content Provider is called just when the application launches as we have registered this in Manifest.
Then, onCreate of our first Activity, i.e. onCreate of ToDodOverviewActivity.
the call to fillData() has the initLoader() call which in turn calls the onCreateLoader of the Loader.
Then, here at
CursorLoader cursorLoader = new CursorLoader(this,
MyTodoContentProvider.CONTENT_URI, projection, null, null, null);
creation of Loader takes place. a loader that queries the ContentResolver and returns a Cursor. This class implements the Loader protocol in a standard way for querying cursors, building on AsyncTaskLoader to perform the cursor query on a background thread so that it does not block the application's UI.
This in turn leads a call to onCreate() of DataHelper and ToDoTable etc and here everyone know the flow about SQLiteOpenHelper.
Finally, onLoadFinished() gets called which in turn swaps the cursor and updates the adapter.