summaryrefslogtreecommitdiffstats
path: root/core/java/android/app
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2010-12-15 14:57:25 -0800
committerDianne Hackborn <hackbod@google.com>2010-12-16 20:09:13 -0800
commitc91893511dc1b9e634648406c9ae61b15476e65d (patch)
tree3ad578a43734d2ed63403a8b9076c2c5c6d07dbe /core/java/android/app
parent60e41fa4456ce6bc37a33b1e4b81a56e9411199b (diff)
downloadframeworks_base-c91893511dc1b9e634648406c9ae61b15476e65d.zip
frameworks_base-c91893511dc1b9e634648406c9ae61b15476e65d.tar.gz
frameworks_base-c91893511dc1b9e634648406c9ae61b15476e65d.tar.bz2
Fix issue #3272082: Contacts: when going back from edit view,
list UI is not ready yet This involves some reworking of Loaders. Loaders, in particular CursorLoader, are now expected to retain their current data after being stopped. This allows applications to keep that data across onStop() -> onStart(), so when the user returns to the app it doesn't have to wait for the data to reload and thus cause flicker. This includes various API changes to better reflect the new semantics, plus a new LoaderCallbacks method to tell the application when it is actually time to stop their use of a loader's data. Note this is somewhat half-done, to help checking in the extensive application changes that are required without causing build breakage. Change-Id: Ib4b3bf8185a6da46e7f06ca125521d65e2e380a1
Diffstat (limited to 'core/java/android/app')
-rw-r--r--core/java/android/app/LoaderManager.java123
-rw-r--r--core/java/android/app/LoaderManagingFragment.java207
2 files changed, 107 insertions, 223 deletions
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 0ab987a..e5db00d 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -26,7 +26,19 @@ import java.io.PrintWriter;
/**
* Interface associated with an {@link Activity} or {@link Fragment} for managing
- * one or more {@link android.content.Loader} instances associated with it.
+ * one or more {@link android.content.Loader} instances associated with it. This
+ * helps an application manage longer-running operations in conjunction with the
+ * Activity or Fragment lifecycle; the most common use of this is with a
+ * {@link android.content.CursorLoader}, however applications are free to write
+ * their own loaders for loading other types of data.
+ *
+ * <p>As an example, here is the full implementation of a {@link Fragment}
+ * that displays a {@link android.widget.ListView} containing the results of
+ * a query against the contacts content provider. It uses a
+ * {@link android.content.CursorLoader} to manage the query on the provider.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
+ * fragment_cursor}
*/
public abstract class LoaderManager {
/**
@@ -49,10 +61,48 @@ public abstract class LoaderManager {
* activity's state is saved. See {@link FragmentManager#openTransaction()
* FragmentManager.openTransaction()} for further discussion on this.
*
+ * <p>This function is guaranteed to be called prior to the release of
+ * the last data that was supplied for this Loader. At this point
+ * you should remove all use of the old data (since it will be released
+ * soon), but should not do your own release of the data since its Loader
+ * owns it and will take care of that. The Loader will take care of
+ * management of its data so you don't have to. In particular:
+ *
+ * <ul>
+ * <li> <p>The Loader will monitor for changes to the data, and report
+ * them to you through new calls here. You should not monitor the
+ * data yourself. For example, if the data is a {@link android.database.Cursor}
+ * and you place it in a {@link android.widget.CursorAdapter}, use
+ * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
+ * android.database.Cursor, int)} constructor <em>without</em> passing
+ * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
+ * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
+ * (that is, use 0 for the flags argument). This prevents the CursorAdapter
+ * from doing its own observing of the Cursor, which is not needed since
+ * when a change happens you will get a new Cursor throw another call
+ * here.
+ * <li> The Loader will release the data once it knows the application
+ * is no longer using it. For example, if the data is
+ * a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
+ * you should not call close() on it yourself. If the Cursor is being placed in a
+ * {@link android.widget.CursorAdapter}, you should use the
+ * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
+ * method so that the old Cursor is not closed.
+ * </ul>
+ *
* @param loader The Loader that has finished.
* @param data The data generated by the Loader.
*/
public void onLoadFinished(Loader<D> loader, D data);
+
+ /**
+ * Called when a previously created loader is being reset, and thus
+ * making its data unavailable. The application should at this point
+ * remove any references it has to the Loader's data.
+ *
+ * @param loader The Loader that is being reset.
+ */
+ public void onLoaderReset(Loader<D> loader);
}
/**
@@ -65,20 +115,33 @@ public abstract class LoaderManager {
* will be called as the loader state changes. If at the point of call
* the caller is in its started state, and the requested loader
* already exists and has generated its data, then
- * callback. {@link LoaderCallbacks#onLoadFinished} will
+ * callback {@link LoaderCallbacks#onLoadFinished} will
* be called immediately (inside of this function), so you must be prepared
* for this to happen.
+ *
+ * @param id A unique identifier for this loader. Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param args Optional arguments to supply to the loader at construction.
+ * @param callback Interface the LoaderManager will call to report about
+ * changes in the state of the loader. Required.
*/
public abstract <D> Loader<D> initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
/**
- * Creates a new loader in this manager, registers the callbacks to it,
+ * Starts a new or restarts an existing {@link android.content.Loader} in
+ * this manager, registers the callbacks to it,
* and (if the activity/fragment is currently started) starts loading it.
* If a loader with the same id has previously been
* started it will automatically be destroyed when the new loader completes
* its work. The callback will be delivered before the old loader
* is destroyed.
+ *
+ * @param id A unique identifier for this loader. Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param args Optional arguments to supply to the loader at construction.
+ * @param callback Interface the LoaderManager will call to report about
+ * changes in the state of the loader. Required.
*/
public abstract <D> Loader<D> restartLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
@@ -86,7 +149,15 @@ public abstract class LoaderManager {
/**
* Stops and removes the loader with the given ID.
*/
- public abstract void stopLoader(int id);
+ public abstract void destroyLoader(int id);
+
+ /**
+ * @deprecated Renamed to {@link #destroyLoader}.
+ */
+ @Deprecated
+ public void stopLoader(int id) {
+ destroyLoader(id);
+ }
/**
* Return the Loader with the given id or null if no matching Loader
@@ -191,13 +262,16 @@ class LoaderManagerImpl extends LoaderManager {
stop();
}
}
- if (mStarted && mData != null) {
- // This loader was retained, and now at the point of
- // finishing the retain we find we remain started, have
- // our data, and the owner has a new callback... so
- // let's deliver the data now.
- callOnLoadFinished(mLoader, mData);
- }
+ }
+
+ if (mStarted && mData != null) {
+ // This loader has retained its data, either completely across
+ // a configuration change or just whatever the last data set
+ // was after being restarted from a stop, and now at the point of
+ // finishing the retain we find we remain started, have
+ // our data, and the owner has a new callback... so
+ // let's deliver the data now.
+ callOnLoadFinished(mLoader, mData);
}
}
@@ -211,20 +285,34 @@ class LoaderManagerImpl extends LoaderManager {
mLoader.unregisterListener(this);
mLoader.stopLoading();
}
- mData = null;
}
}
void destroy() {
if (DEBUG) Log.v(TAG, " Destroying: " + this);
mDestroyed = true;
+ if (mCallbacks != null && mLoader != null && mData != null) {
+ String lastBecause = null;
+ if (mActivity != null) {
+ lastBecause = mActivity.mFragments.mNoTransactionsBecause;
+ mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset";
+ }
+ try {
+ mCallbacks.onLoaderReset(mLoader);
+ } finally {
+ if (mActivity != null) {
+ mActivity.mFragments.mNoTransactionsBecause = lastBecause;
+ }
+ }
+ }
mCallbacks = null;
+ mData = null;
if (mLoader != null) {
if (mListenerRegistered) {
mListenerRegistered = false;
mLoader.unregisterListener(this);
}
- mLoader.destroy();
+ mLoader.reset();
}
}
@@ -238,7 +326,9 @@ class LoaderManagerImpl extends LoaderManager {
// Notify of the new data so the app can switch out the old data before
// we try to destroy it.
mData = data;
- callOnLoadFinished(loader, data);
+ if (mStarted) {
+ callOnLoadFinished(loader, data);
+ }
if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
@@ -388,7 +478,7 @@ class LoaderManagerImpl extends LoaderManager {
return (Loader<D>)info.mLoader;
}
- public void stopLoader(int id) {
+ public void destroyLoader(int id) {
if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + id);
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
@@ -416,12 +506,13 @@ class LoaderManagerImpl extends LoaderManager {
return;
}
+ mStarted = true;
+
// Call out to sub classes so they can start their loaders
// Let the existing loaders know that we want to be notified when a load is complete
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).start();
}
- mStarted = true;
}
void doStop() {
diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java
deleted file mode 100644
index f0f5856..0000000
--- a/core/java/android/app/LoaderManagingFragment.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app;
-
-import android.content.Loader;
-import android.os.Bundle;
-
-import java.util.HashMap;
-
-/**
- * A Fragment that has utility methods for managing {@link Loader}s.
- *
- * @param <D> The type of data returned by the Loader. If you're using multiple Loaders with
- * different return types use Object and case the results.
- *
- * @deprecated This was an old design, it will be removed before Honeycomb ships.
- */
-@Deprecated
-public abstract class LoaderManagingFragment<D> extends Fragment
- implements Loader.OnLoadCompleteListener<D> {
- private boolean mStarted = false;
-
- static final class LoaderInfo<D> {
- public Bundle args;
- public Loader<D> loader;
- }
- private HashMap<Integer, LoaderInfo<D>> mLoaders;
- private HashMap<Integer, LoaderInfo<D>> mInactiveLoaders;
-
- /**
- * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
- * If a loader with the same id has previously been started it will automatically be destroyed
- * when the new loader completes it's work. The callback will be delivered before the old loader
- * is destroyed.
- */
- public Loader<D> startLoading(int id, Bundle args) {
- LoaderInfo<D> info = mLoaders.get(id);
- if (info != null) {
- // Keep track of the previous instance of this loader so we can destroy
- // it when the new one completes.
- mInactiveLoaders.put(id, info);
- }
- info = new LoaderInfo<D>();
- info.args = args;
- mLoaders.put(id, info);
- Loader<D> loader = onCreateLoader(id, args);
- info.loader = loader;
- if (mStarted) {
- // The activity will start all existing loaders in it's onStart(), so only start them
- // here if we're past that point of the activitiy's life cycle
- loader.registerListener(id, this);
- loader.startLoading();
- }
- return loader;
- }
-
- protected abstract Loader<D> onCreateLoader(int id, Bundle args);
- protected abstract void onInitializeLoaders();
- protected abstract void onLoadFinished(Loader<D> loader, D data);
-
- public final void onLoadComplete(Loader<D> loader, D data) {
- // Notify of the new data so the app can switch out the old data before
- // we try to destroy it.
- onLoadFinished(loader, data);
-
- // Look for an inactive loader and destroy it if found
- int id = loader.getId();
- LoaderInfo<D> info = mInactiveLoaders.get(id);
- if (info != null) {
- Loader<D> oldLoader = info.loader;
- if (oldLoader != null) {
- oldLoader.destroy();
- }
- mInactiveLoaders.remove(id);
- }
- }
-
- @Override
- public void onCreate(Bundle savedState) {
- super.onCreate(savedState);
-
- if (mLoaders == null) {
- // Look for a passed along loader and create a new one if it's not there
-// TODO: uncomment once getLastNonConfigurationInstance method is available
-// mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
- if (mLoaders == null) {
- mLoaders = new HashMap<Integer, LoaderInfo<D>>();
- onInitializeLoaders();
- }
- }
- if (mInactiveLoaders == null) {
- mInactiveLoaders = new HashMap<Integer, LoaderInfo<D>>();
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- // Call out to sub classes so they can start their loaders
- // Let the existing loaders know that we want to be notified when a load is complete
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- int id = entry.getKey();
- if (loader == null) {
- loader = onCreateLoader(id, info.args);
- info.loader = loader;
- }
- loader.registerListener(id, this);
- loader.startLoading();
- }
-
- mStarted = true;
- }
-
- @Override
- public void onStop() {
- super.onStop();
-
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- if (loader == null) {
- continue;
- }
-
- // Let the loader know we're done with it
- loader.unregisterListener(this);
-
- // The loader isn't getting passed along to the next instance so ask it to stop loading
- if (!getActivity().isChangingConfigurations()) {
- loader.stopLoading();
- }
- }
-
- mStarted = false;
- }
-
- /* TO DO: This needs to be turned into a retained fragment.
- @Override
- public Object onRetainNonConfigurationInstance() {
- // Pass the loader along to the next guy
- Object result = mLoaders;
- mLoaders = null;
- return result;
- }
- **/
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mLoaders != null) {
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- if (loader == null) {
- continue;
- }
- loader.destroy();
- }
- }
- }
-
- /**
- * Stops and removes the loader with the given ID.
- */
- public void stopLoading(int id) {
- if (mLoaders != null) {
- LoaderInfo<D> info = mLoaders.remove(id);
- if (info != null) {
- Loader<D> loader = info.loader;
- if (loader != null) {
- loader.unregisterListener(this);
- loader.destroy();
- }
- }
- }
- }
-
- /**
- * @return the Loader with the given id or null if no matching Loader
- * is found.
- */
- public Loader<D> getLoader(int id) {
- LoaderInfo<D> loaderInfo = mLoaders.get(id);
- if (loaderInfo != null) {
- return mLoaders.get(id).loader;
- }
- return null;
- }
-}