Loading the List of Runs - Lifecycle Q's


#1

The instruction in onLoadReset() bugs me:

Because that method does not seem tied to the usual lifecycle events of the Fragment.

So, I added the other lifecycle methods to see if there’s a connection. There doesn’t seem to be one. I am seeing this in LogCat:

09-04 23:16:34.220: E/RunListFragment(2212): onPause() called 09-04 23:16:34.220: E/RunListFragment(2212): onStop() called 09-04 23:16:34.230: E/RunListFragment(2212): onDestroyView() called 09-04 23:16:34.230: E/RunListFragment(2212): onDestroy() called

But I only see the following when I click the Android back-button:

09-04 23:22:15.900: E/RunListFragment(2212): onPause() called 09-04 23:22:17.017: E/RunListFragment(2212): onStop() called 09-04 23:22:17.042: E/RunListFragment(2212): onDestroyView() called 09-04 23:22:17.042: E/RunListFragment(2212): onLoaderReset() called <<=== This one 09-04 23:22:17.120: E/RunListFragment(2212): onDestroy() called

Then I changed the code in onCreate() as so:

if (getListAdapter() == null) { getLoaderManager().initLoader(0, null, this); }

This works! No need to reload the list if it wasn’t destroyed. It seems that the inner adapter has a life of its own, and this applies to the Fragment’s LoadManager as well.

So, the usual lifecycle methods we can overload are only used for the state of the UI? I thought onDestroy() signaled the death of the Fragment, and onCreate() the creation of everything (the instance of our class (RunListFragment) and its variables and the inflation of its view).

I am puzzled that the adapter survives. :open_mouth:

EDIT:
OOPS! It’s always null in onCreate(). However, that being the case, why do we need to set the adapter to null in onLoaderReset(), when the whole Fragment is being killed anyway?


#2

Nice sleuthing, RickDroid!

As of your edit, it looks like you found out what’s basically going on, but I will try to elaborate a bit.

The purpose of the onLoaderReset() callback is to give you an opportunity to get rid of any references to the data you’re using from the loader. For example, if the loader is using a Cursor, and that cursor has a tie to some native resources and stuff, it needs to have close() called on it at some point. In our implementation, this is handled in SQLiteCursorLoader’s onReset() method, which happens just before the call to onLoaderReset() in the callback implementation.

Getting rid of the adapter altogether is just an easy way to get rid of the reference it holds to the cursor, in this case. The adapter will be going away too, so it’s more of a prudent measure than anything.

For a great in-depth discussion of Loaders, LoaderManager and all the callbacks, I highly recommend Alex Lockwood’s series:

androiddesignpatterns.com/20 … nager.html


#3

Thanks for that link! I put Log instructions in every relevant method and within their IF’s to see what is happening. I feel like I am cutting and pasting code without understanding it. I’m not sure if it helps much, but here it is anyway. Like note how rotating the device causes the Fragment to get destroyed and recreated, but the database stuff is still there.

[size=150]Method Path on Startup:[/size]

RunListFragment.onCreate() RunListFragment.onCreateLoader() RunListFragment.onCreate().getLoaderManager().initLoader() SQLiteCursorLoader.onStartLoading() SQLiteCursorLoader.onStartLoading().forceLoad() SQLiteCursorLoader.loadInBackground() RunListCursorLoader.loadCursor() SQLiteCursorLoader.loadInBackground().cursor.getCount() SQLiteCursorLoader.deliverResult() SQLiteCursorLoader.deliverResult().super.deliverResult(data) RunListFragment.onLoadFinished()
[size=150]Method Path on Back-button:[/size]

RunListFragment.onPause() RunListFragment.onStop() SQLiteCursorLoader.onStopLoading() RunListFragment.onDestroyView() RunListFragment.onLoaderReset() SQLiteCursorLoader.onReset() SQLiteCursorLoader.onStopLoading() SQLiteCursorLoader.onReset().mCursor.close() RunListFragment.onDestroy()
[size=150]Method Path on Rotate:[/size]

RunListFragment.onPause() RunListFragment.onStop() RunListFragment.onDestroyView() RunListFragment.onDestroy() RunListFragment.onCreate() RunListFragment.onCreate().getLoaderManager().initLoader() RunListFragment.onLoadFinished() RunListFragment.onLoadFinished()
[size=150]Method Path on Navigating Away And Back With Recents-Button or Home-Button & Click App Icon[/size]

code
RunListFragment.onPause()
RunListFragment.onStop()
SQLiteCursorLoader.onStopLoading()
(BACK)
SQLiteCursorLoader.onStartLoading()
SQLiteCursorLoader.onStartLoading().deliverResult()
SQLiteCursorLoader.deliverResult()
SQLiteCursorLoader.deliverResult().super.deliverResult(data)[/code]
[size=150]Method Path When Creating a New Run[/size]

(Click New Run Button) RunListFragment.onPause() RunListFragment.onStop() SQLiteCursorLoader.onStopLoading() (Back to ListFragment) RunListFragment.onActivityResult() RunListFragment.onCreateLoader() SQLiteCursorLoader.onStartLoading() SQLiteCursorLoader.onStartLoading().forceLoad() SQLiteCursorLoader.loadInBackground() RunListCursorLoader.loadCursor() SQLiteCursorLoader.loadInBackground().cursor.getCount() SQLiteCursorLoader.deliverResult() called SQLiteCursorLoader.deliverResult().super.deliverResult(data) RunListFragment.onLoadFinished() SQLiteCursorLoader.onReset() SQLiteCursorLoader.onStopLoading() SQLiteCursorLoader.onReset().mCursor.close()


#4

I’m not sure I understand your question at this point, but I think you’re asking about the reason that the loader lifecycle differs from the fragment lifecycle during rotation.

The answer, in short, is that this is one of the key features of the Loader and LoaderManager.

When you rotate the device, a configuration change occurs (changing the orientation). This configuration change, in the normal case, triggers a destruction and recreation of the current foreground Activity. That, in turn, triggers a destruction and recreation of any Fragments attached to it (if they don’t have setRetainInstance(true) going for them). Without a Loader in the mix, you would have to be responsible for finding a way to ensure that any data you’ve fetched from some expensive location (like a database or the web) was stashed appropriately so that it wasn’t needlessly re-fetched in the new Activity/Fragment.

The LoaderManager changes this for the better. When you’re using a Loader, the LoaderManager will be responsible for keeping that Loader and its data alive through the configuration change (rotation), and it will re-deliver it to the new Activity or Fragment on the other side of the event. It can also handle the situation where the Loader is still loading data while that rotation event is happening, which is another place that the pre-loader API left you to figure out.

Hope this is helpful.


#5

I was just showing that in case it helps anyone. The link you gave explains it pretty well, especially how the LoadManager does its own thing to preserve its state, which explains what I was seeing.

Thanks.