diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index fb2b75905bbf7..a37acb9646a15 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -194,6 +194,14 @@ + +
  • + Content Providers +
  • +
  • + Loaders + new!
  • +
  • Security and Permissions
  • diff --git a/docs/html/guide/topics/providers/loaders.jd b/docs/html/guide/topics/providers/loaders.jd new file mode 100644 index 0000000000000..c54656c01f823 --- /dev/null +++ b/docs/html/guide/topics/providers/loaders.jd @@ -0,0 +1,492 @@ +page.title=Using Loaders +@jd:body +
    +
    +

    In this document

    +
      +
    1. Loader API Summary
    2. +
    3. Using Loaders in an Application +
        +
      1. +
      2. Starting a Loader
      3. +
      4. Restarting a Loader
      5. +
      6. Using the LoaderManager Callbacks
      7. +
      +
    4. +
    5. Example +
        +
      1. More Examples
      2. +
      +
    6. +
    + +

    Key classes

    +
      +
    1. {@link android.app.LoaderManager}
    2. +
    3. {@link android.content.Loader}
    4. + +
    + +

    Related samples

    +
      +
    1. FragmentCursorLoader
    2. +
    3. LoaderThrottle
    4. +
    +
    +
    + +

    Introduced in Android 3.0, loaders make it easy to asynchronously load data +in an activity or fragment. Loaders have these characteristics:

    + + +

    Loader API Summary

    + +

    There are multiple classes and interfaces that may be involved in using +loaders in an application. They are summarized in this table:

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class/InterfaceDescription
    {@link android.app.LoaderManager}An abstract class associated with an {@link android.app.Activity} or +{@link android.app.Fragment} for managing one or more {@link +android.content.Loader} instances. This helps an application manage +longer-running operations in conjunction with the {@link android.app.Activity} +or {@link android.app.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. +
    +
    + There is only one {@link android.app.LoaderManager} per activity or fragment. But a {@link android.app.LoaderManager} can have +multiple loaders.
    {@link android.app.LoaderManager.LoaderCallbacks}A callback interface for a client to interact with the {@link +android.app.LoaderManager}. For example, you use the {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +callback method to create a new loader.
    {@link android.content.Loader}An abstract class that performs asynchronous loading of data. This is +the base class for a loader. You would typically use {@link +android.content.CursorLoader}, but you can implement your own subclass. While +loaders are active they should monitor the source of their data and deliver new +results when the contents change.
    {@link android.content.AsyncTaskLoader}Abstract loader that provides an {@link android.os.AsyncTask} to do the work.
    {@link android.content.CursorLoader}A subclass of {@link android.content.AsyncTaskLoader} that queries the +{@link android.content.ContentResolver} and returns a {@link +android.database.Cursor}. This class implements the {@link +android.content.Loader} protocol in a standard way for querying cursors, +building on {@link android.content.AsyncTaskLoader} to perform the cursor query +on a background thread so that it does not block the application's UI. Using +this loader is the best way to asynchronously load data from a {@link +android.content.ContentProvider}, instead of performing a managed query through +the fragment or activity's APIs.
    + +

    The classes and interfaces in the above table are the essential components +you'll use to implement a loader in your application. You won't need all of them +for each loader you create, but you'll always need a reference to the {@link +android.app.LoaderManager} in order to initialize a loader and an implementation +of a {@link android.content.Loader} class such as {@link +android.content.CursorLoader}. The following sections show you how to use these +classes and interfaces in an application.

    + +

    Using Loaders in an Application

    +

    This section describes how to use loaders in an Android application. An +application that uses loaders typically includes the following:

    + +

    Starting a Loader

    + +

    The {@link android.app.LoaderManager} manages one or more {@link +android.content.Loader} instances within an {@link android.app.Activity} or +{@link android.app.Fragment}. There is only one {@link +android.app.LoaderManager} per activity or fragment.

    + +

    You typically +initialize a {@link android.content.Loader} within the activity's {@link +android.app.Activity#onCreate onCreate()} method, or within the fragment's +{@link android.app.Fragment#onActivityCreated onActivityCreated()} method. You +do this as follows:

    + +
    // Prepare the loader.  Either re-connect with an existing one,
    +// or start a new one.
    +getLoaderManager().initLoader(0, null, this);
    + +

    The {@link android.app.LoaderManager#initLoader initLoader()} method takes +the following parameters:

    + +

    The {@link android.app.LoaderManager#initLoader initLoader()} call ensures that a loader +is initialized and active. It has two possible outcomes:

    + +

    In either case, the given {@link android.app.LoaderManager.LoaderCallbacks} +implementation is associated with the loader, and will be called when the +loader state changes. If at the point of this call the caller is in its +started state, and the requested loader already exists and has generated its +data, then the system calls {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +immediately (during {@link android.app.LoaderManager#initLoader initLoader()}), +so you must be prepared for this to happen. See +onLoadFinished for more discussion of this callback

    + +

    Note that the {@link android.app.LoaderManager#initLoader initLoader()} +method returns the {@link android.content.Loader} that is created, but you don't +need to capture a reference to it. The {@link android.app.LoaderManager} manages +the life of the loader automatically. The {@link android.app.LoaderManager} +starts and stops loading when necessary, and maintains the state of the loader +and its associated content. As this implies, you rarely interact with loaders +directly (though for an example of using loader methods to fine-tune a loader's +behavior, see the LoaderThrottle sample). +You most commonly use the {@link +android.app.LoaderManager.LoaderCallbacks} methods to intervene in the loading +process when particular events occur. For more discussion of this topic, see Using the LoaderManager Callbacks.

    + +

    Restarting a Loader

    + +

    When you use {@link android.app.LoaderManager#initLoader initLoader()}, as +shown above, it uses an existing loader with the specified ID if there is one. +If there isn't, it creates one. But sometimes you want to discard your old data +and start over.

    + +

    To discard your old data, you use {@link +android.app.LoaderManager#restartLoader restartLoader()}. For example, this +implementation of {@link android.widget.SearchView.OnQueryTextListener} restarts +the loader when the user's query changes. The loader needs to be restarted so +that it can use the revised search filter to do a new query:

    + +
    +public boolean onQueryTextChanged(String newText) {
    +    // Called when the action bar search text has changed.  Update
    +    // the search filter, and restart the loader to do a new query
    +    // with this filter.
    +    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    +    getLoaderManager().restartLoader(0, null, this);
    +    return true;
    +}
    + +

    Using the LoaderManager Callbacks

    + +

    {@link android.app.LoaderManager.LoaderCallbacks} is a callback interface +that lets a client interact with the {@link android.app.LoaderManager}.

    +

    Loaders, in particular {@link android.content.CursorLoader}, are expected to +retain their data after being stopped. This allows applications to keep their +data across the activity or fragment's {@link android.app.Activity#onStop +onStop()} and {@link android.app.Activity#onStart onStart()} methods, so that +when users return to an application, they don't have to wait for the data to +reload. You use the {@link android.app.LoaderManager.LoaderCallbacks} methods +when to know when to create a new loader, and to tell the application when it is + time to stop using a loader's data.

    + +

    {@link android.app.LoaderManager.LoaderCallbacks} includes these +methods:

    + + + +

    These methods are described in more detail in the following sections.

    + +

    onCreateLoader

    + +

    When you attempt to access a loader (for example, through {@link +android.app.LoaderManager#initLoader initLoader()}), it checks to see whether +the loader specified by the ID exists. If it doesn't, it triggers the {@link +android.app.LoaderManager.LoaderCallbacks} method {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. This +is where you create a new loader. Typically this will be a {@link +android.content.CursorLoader}, but you can implement your own {@link +android.content.Loader} subclass.

    + +

    In this example, the {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +callback method creates a {@link android.content.CursorLoader}. You must build +the {@link android.content.CursorLoader} using its constructor method, which +requires the complete set of information needed to perform a query to the {@link +android.content.ContentProvider}. Specifically, it needs:

    + +

    For example:

    +
    + // If non-null, this is the current filter the user has provided.
    +String mCurFilter;
    +...
    +public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    +    // This is called when a new Loader needs to be created.  This
    +    // sample only has one Loader, so we don't care about the ID.
    +    // First, pick the base URI to use depending on whether we are
    +    // currently filtering.
    +    Uri baseUri;
    +    if (mCurFilter != null) {
    +        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
    +                  Uri.encode(mCurFilter));
    +    } else {
    +        baseUri = Contacts.CONTENT_URI;
    +    }
    +
    +    // Now create and return a CursorLoader that will take care of
    +    // creating a Cursor for the data being displayed.
    +    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
    +            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
    +            + Contacts.DISPLAY_NAME + " != '' ))";
    +    return new CursorLoader(getActivity(), baseUri,
    +            CONTACTS_SUMMARY_PROJECTION, select, null,
    +            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    +}
    +

    onLoadFinished

    + +

    This method is called when a previously created loader has finished its load. +This method 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 release the data once it knows the application is no longer +using it. For example, if the data is a cursor from a {@link +android.content.CursorLoader}, you should not call {@link +android.database.Cursor#close close()} on it yourself. If the cursor is being +placed in a {@link android.widget.CursorAdapter}, you should use the {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} method so that the +old {@link android.database.Cursor} is not closed. For example:

    + +
    +// This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
    + +

    onLoaderReset

    + +

    This method is called when a previously created loader is being reset, thus +making its data unavailable. This callback lets you find out when the data is +about to be released so you can remove your reference to it.  

    +

    This implementation calls +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +with a value of null:

    + +
    +// This is the Adapter being used to display the list's data.
    +SimpleCursorAdapter mAdapter;
    +...
    +
    +public void onLoaderReset(Loader<Cursor> loader) {
    +    // This is called when the last Cursor provided to onLoadFinished()
    +    // above is about to be closed.  We need to make sure we are no
    +    // longer using it.
    +    mAdapter.swapCursor(null);
    +}
    + + +

    Example

    + +

    As an example, here is the full implementation of a {@link +android.app.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.

    + +

    For an application to access a user's contacts, as shown in this example, its +manifest must include the permission +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.

    + +
    +public static class CursorLoaderListFragment extends ListFragment
    +        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
    +
    +    // This is the Adapter being used to display the list's data.
    +    SimpleCursorAdapter mAdapter;
    +
    +    // If non-null, this is the current filter the user has provided.
    +    String mCurFilter;
    +
    +    @Override public void onActivityCreated(Bundle savedInstanceState) {
    +        super.onActivityCreated(savedInstanceState);
    +
    +        // Give some text to display if there is no data.  In a real
    +        // application this would come from a resource.
    +        setEmptyText("No phone numbers");
    +
    +        // We have a menu item to show in action bar.
    +        setHasOptionsMenu(true);
    +
    +        // Create an empty adapter we will use to display the loaded data.
    +        mAdapter = new SimpleCursorAdapter(getActivity(),
    +                android.R.layout.simple_list_item_2, null,
    +                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
    +                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
    +        setListAdapter(mAdapter);
    +
    +        // Prepare the loader.  Either re-connect with an existing one,
    +        // or start a new one.
    +        getLoaderManager().initLoader(0, null, this);
    +    }
    +
    +    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    +        // Place an action bar item for searching.
    +        MenuItem item = menu.add("Search");
    +        item.setIcon(android.R.drawable.ic_menu_search);
    +        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    +        SearchView sv = new SearchView(getActivity());
    +        sv.setOnQueryTextListener(this);
    +        item.setActionView(sv);
    +    }
    +
    +    public boolean onQueryTextChange(String newText) {
    +        // Called when the action bar search text has changed.  Update
    +        // the search filter, and restart the loader to do a new query
    +        // with this filter.
    +        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    +        getLoaderManager().restartLoader(0, null, this);
    +        return true;
    +    }
    +
    +    @Override public boolean onQueryTextSubmit(String query) {
    +        // Don't care about this.
    +        return true;
    +    }
    +
    +    @Override public void onListItemClick(ListView l, View v, int position, long id) {
    +        // Insert desired behavior here.
    +        Log.i("FragmentComplexList", "Item clicked: " + id);
    +    }
    +
    +    // These are the Contacts rows that we will retrieve.
    +    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
    +        Contacts._ID,
    +        Contacts.DISPLAY_NAME,
    +        Contacts.CONTACT_STATUS,
    +        Contacts.CONTACT_PRESENCE,
    +        Contacts.PHOTO_ID,
    +        Contacts.LOOKUP_KEY,
    +    };
    +    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    +        // This is called when a new Loader needs to be created.  This
    +        // sample only has one Loader, so we don't care about the ID.
    +        // First, pick the base URI to use depending on whether we are
    +        // currently filtering.
    +        Uri baseUri;
    +        if (mCurFilter != null) {
    +            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
    +                    Uri.encode(mCurFilter));
    +        } else {
    +            baseUri = Contacts.CONTENT_URI;
    +        }
    +
    +        // Now create and return a CursorLoader that will take care of
    +        // creating a Cursor for the data being displayed.
    +        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
    +                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
    +                + Contacts.DISPLAY_NAME + " != '' ))";
    +        return new CursorLoader(getActivity(), baseUri,
    +                CONTACTS_SUMMARY_PROJECTION, select, null,
    +                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    +    }
    +
    +    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    +        // Swap the new cursor in.  (The framework will take care of closing the
    +        // old cursor once we return.)
    +        mAdapter.swapCursor(data);
    +    }
    +
    +    public void onLoaderReset(Loader<Cursor> loader) {
    +        // This is called when the last Cursor provided to onLoadFinished()
    +        // above is about to be closed.  We need to make sure we are no
    +        // longer using it.
    +        mAdapter.swapCursor(null);
    +    }
    +}
    +

    More Examples

    + +

    There are a few different samples in ApiDemos that +illustrate how to use loaders:

    + + +

    For information on downloading and installing the SDK samples, see Getting the +Samples.

    +