diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-05-29 13:58:13 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2012-06-04 11:12:37 -0700 |
commit | 80df91c7f2ac155c1cf4c3337f8db5a4bf5426b5 (patch) | |
tree | c68c4762a2ff2544d04262a2fbcbd0e95724ab3f | |
parent | 50d57364f97135906e3acd627d8c3327057d4bb4 (diff) | |
download | android_development-80df91c7f2ac155c1cf4c3337f8db5a4bf5426b5.tar.gz android_development-80df91c7f2ac155c1cf4c3337f8db5a4bf5426b5.tar.bz2 android_development-80df91c7f2ac155c1cf4c3337f8db5a4bf5426b5.zip |
New API demos for retained fragments with loaders.
Also tweak the cursor loader samples to filter out query
callbacks that don't change the filter, to avoid restarting the
loader on a configuration change.
Change-Id: Iac9293fed45e127698be59262d68b0b59a8ec9ce
11 files changed, 429 insertions, 8 deletions
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index 037515310..54e972323 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -419,6 +419,15 @@ android:enabled="@bool/atLeastHoneycomb" /> <!-- END_INCLUDE(loader_throttle) --> + <activity android:name=".app.LoaderRetained" + android:label="@string/loader_retained" + android:enabled="@bool/atLeastHoneycomb"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.SAMPLE_CODE" /> + </intent-filter> + </activity> + <!-- Intent Samples --> <activity android:name=".app.Intents" android:label="@string/activity_intents"> diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index c993b0105..a64de167c 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -165,6 +165,8 @@ <string name="loader_cursor">App/Loader/Cursor</string> + <string name="loader_retained">App/Loader/Retained</string> + <string name="loader_custom">App/Loader/Custom</string> <string name="loader_throttle">App/Loader/Throttle</string> diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java index fd2fa6850..f30919146 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java @@ -95,7 +95,8 @@ public class LoaderCursor extends Activity { // 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); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); @@ -105,7 +106,16 @@ public class LoaderCursor extends Activity { // 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; + String newFilter = !TextUtils.isEmpty(newText) ? newText : null; + // Don't do anything if the filter hasn't actually changed. + // Prevents restarting the loader when restoring state. + if (mCurFilter == null && newFilter == null) { + return true; + } + if (mCurFilter != null && mCurFilter.equals(newFilter)) { + return true; + } + mCurFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java index e1e77a99b..7c16fb359 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java @@ -431,7 +431,8 @@ public class LoaderCustom extends Activity { // 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); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java new file mode 100644 index 000000000..521f6e5b2 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 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 com.example.android.apis.app; + +import android.app.Activity; +import android.app.FragmentManager; +import android.app.ListFragment; +import android.app.LoaderManager; +import android.content.CursorLoader; +import android.content.Loader; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.SimpleCursorAdapter; +import android.widget.SearchView.OnQueryTextListener; + +/** + * Demonstration of the use of a CursorLoader to load and display contacts + * data in a fragment. + */ +public class LoaderRetained extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FragmentManager fm = getFragmentManager(); + + // Create the list fragment and add it as our sole content. + if (fm.findFragmentById(android.R.id.content) == null) { + CursorLoaderListFragment list = new CursorLoaderListFragment(); + fm.beginTransaction().add(android.R.id.content, list).commit(); + } + } + +//BEGIN_INCLUDE(fragment_cursor) + 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); + + // In this sample we are going to use a retained fragment. + setRetainInstance(true); + + // 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); + + // Start out with a progress indicator. + setListShown(false); + + // 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 + | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + 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. + String newFilter = !TextUtils.isEmpty(newText) ? newText : null; + // Don't do anything if the filter hasn't actually changed. + // Prevents restarting the loader when restoring state. + if (mCurFilter == null && newFilter == null) { + return true; + } + if (mCurFilter != null && mCurFilter.equals(newFilter)) { + return true; + } + mCurFilter = newFilter; + 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); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + } + + 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); + } + } +//END_INCLUDE(fragment_cursor) +} diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java index 38be2479e..57f0e10f4 100644 --- a/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java +++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java @@ -74,7 +74,8 @@ public class CursorFragment extends ListFragment // 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); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); diff --git a/samples/Support4Demos/AndroidManifest.xml b/samples/Support4Demos/AndroidManifest.xml index cb34be2d0..1aa2107da 100644 --- a/samples/Support4Demos/AndroidManifest.xml +++ b/samples/Support4Demos/AndroidManifest.xml @@ -198,6 +198,14 @@ </intent-filter> </activity> + <activity android:name=".app.LoaderRetainedSupport" + android:label="@string/loader_retained_support"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> + </intent-filter> + </activity> + <activity android:name=".app.LoaderCustomSupport" android:label="@string/loader_custom_support"> <intent-filter> diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml index 8cd91f6fb..e17974db2 100644 --- a/samples/Support4Demos/res/values/strings.xml +++ b/samples/Support4Demos/res/values/strings.xml @@ -91,7 +91,9 @@ <string name="fragment_state_pager_support">Fragment/State Pager</string> <string name="loader_cursor_support">Loader/Cursor</string> - + + <string name="loader_retained_support">Loader/Retained</string> + <string name="loader_custom_support">Loader/Custom</string> <string name="loader_throttle_support">Loader/Throttle</string> diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java index dd4237b7d..bc74b6d34 100644 --- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java @@ -99,7 +99,8 @@ public class LoaderCursorSupport extends FragmentActivity { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); - MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS + | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); View searchView = SearchViewCompat.newSearchView(getActivity()); if (searchView != null) { SearchViewCompat.setOnQueryTextListener(searchView, @@ -109,7 +110,16 @@ public class LoaderCursorSupport extends FragmentActivity { // 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; + String newFilter = !TextUtils.isEmpty(newText) ? newText : null; + // Don't do anything if the filter hasn't actually changed. + // Prevents restarting the loader when restoring state. + if (mCurFilter == null && newFilter == null) { + return true; + } + if (mCurFilter != null && mCurFilter.equals(newFilter)) { + return true; + } + mCurFilter = newFilter; getLoaderManager().restartLoader(0, null, CursorLoaderListFragment.this); return true; } diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java index ec59738b8..b735e657f 100644 --- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java @@ -438,7 +438,8 @@ public class LoaderCustomSupport extends FragmentActivity { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); - MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM + | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); View searchView = SearchViewCompat.newSearchView(getActivity()); if (searchView != null) { SearchViewCompat.setOnQueryTextListener(searchView, diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderRetainedSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderRetainedSupport.java new file mode 100644 index 000000000..8ea47e399 --- /dev/null +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderRetainedSupport.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012 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 com.example.android.supportv4.app; + +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.MenuItemCompat; +import android.support.v4.widget.SearchViewCompat; +import android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat; +import android.support.v4.widget.SimpleCursorAdapter; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.BaseColumns; +import android.provider.Contacts.People; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; + +/** + * Demonstration of the use of a CursorLoader to load and display contacts + * data in a fragment. + */ +@SuppressWarnings("all") +public class LoaderRetainedSupport extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FragmentManager fm = getSupportFragmentManager(); + + // Create the list fragment and add it as our sole content. + if (fm.findFragmentById(android.R.id.content) == null) { + CursorLoaderListFragment list = new CursorLoaderListFragment(); + fm.beginTransaction().add(android.R.id.content, list).commit(); + } + } + +//BEGIN_INCLUDE(fragment_cursor) + public static class CursorLoaderListFragment extends ListFragment + implements 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); + + // In this sample we are going to use a retained fragment. + setRetainInstance(true); + + // 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_1, null, + new String[] { People.DISPLAY_NAME }, + new int[] { android.R.id.text1}, 0); + setListAdapter(mAdapter); + + // Start out with a progress indicator. + setListShown(false); + + // 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); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS + | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + View searchView = SearchViewCompat.newSearchView(getActivity()); + if (searchView != null) { + SearchViewCompat.setOnQueryTextListener(searchView, + new OnQueryTextListenerCompat() { + @Override + 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. + String newFilter = !TextUtils.isEmpty(newText) ? newText : null; + // Don't do anything if the filter hasn't actually changed. + // Prevents restarting the loader when restoring state. + if (mCurFilter == null && newFilter == null) { + return true; + } + if (mCurFilter != null && mCurFilter.equals(newFilter)) { + return true; + } + mCurFilter = newFilter; + getLoaderManager().restartLoader(0, null, CursorLoaderListFragment.this); + return true; + } + }); + MenuItemCompat.setActionView(item, searchView); + } + } + + @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[] { + People._ID, + People.DISPLAY_NAME, + }; + + 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(People.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); + } else { + baseUri = People.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + People.DISPLAY_NAME + " NOTNULL) AND (" + + People.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + People.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); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + } + + 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); + } + } +//END_INCLUDE(fragment_cursor) +} |