1 /* 2 * Copyright 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.content.loadercursor; 18 19 import android.app.ListFragment; 20 import android.app.LoaderManager; 21 import android.content.CursorLoader; 22 import android.content.Loader; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.provider.ContactsContract.Contacts; 27 import android.text.TextUtils; 28 import android.view.Menu; 29 import android.view.MenuInflater; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.widget.ListView; 33 import android.widget.SearchView; 34 import android.widget.SearchView.OnCloseListener; 35 import android.widget.SearchView.OnQueryTextListener; 36 import android.widget.SimpleCursorAdapter; 37 import android.widget.Toast; 38 39 /** 40 * A {@link ListFragment} that shows the use of a {@link LoaderManager} to 41 * display a list of contacts accessed through a {@link Cursor}. 42 */ 43 public class CursorLoaderListFragment extends ListFragment implements 44 LoaderManager.LoaderCallbacks<Cursor> { 45 46 // This is the Adapter being used to display the list's data. 47 SimpleCursorAdapter mAdapter; 48 49 // The SearchView for doing filtering. 50 SearchView mSearchView; 51 52 // If non-null, this is the current filter the user has provided. 53 String mCurFilter; 54 55 @Override onActivityCreated(Bundle savedInstanceState)56 public void onActivityCreated(Bundle savedInstanceState) { 57 super.onActivityCreated(savedInstanceState); 58 59 // Give some text to display if there is no data. In a real 60 // application this would come from a resource. 61 setEmptyText("No phone numbers"); 62 63 // We have a menu item to show in action bar. 64 setHasOptionsMenu(true); 65 66 /* 67 * Create an empty adapter we will use to display the loaded data. The 68 * simple_list_item_2 layout contains two rows on top of each other 69 * (text1 and text2) that will show the contact's name and status. 70 */ 71 mAdapter = new SimpleCursorAdapter(getActivity(), 72 android.R.layout.simple_list_item_2, null, 73 new String[] { 74 Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS 75 }, 76 new int[] { 77 android.R.id.text1, android.R.id.text2 78 }, 0); 79 setListAdapter(mAdapter); 80 81 // Start out with a progress indicator. 82 setListShown(false); 83 84 // BEGIN_INCLUDE(getloader) 85 // Prepare the loader. Either re-connect with an existing one, 86 // or start a new one. 87 getLoaderManager().initLoader(0, null, this); 88 // END_INCLUDE(getloader) 89 } 90 91 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)92 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 93 94 inflater.inflate(R.menu.main, menu); 95 96 // Get the search item and update its action view 97 MenuItem item = menu.findItem(R.id.action_search); 98 mSearchView = (SearchView) item.getActionView(); 99 mSearchView.setOnQueryTextListener(queryListener); 100 mSearchView.setOnCloseListener(closeListener); 101 mSearchView.setIconifiedByDefault(true); 102 } 103 104 /** 105 * The {@link OnCloseListener} called when the SearchView is closed. Resets 106 * the query field. 107 */ 108 private SearchView.OnCloseListener closeListener = new SearchView.OnCloseListener() { 109 110 @Override 111 public boolean onClose() { 112 // Restore the SearchView if a query was entered 113 if (!TextUtils.isEmpty(mSearchView.getQuery())) { 114 mSearchView.setQuery(null, true); 115 } 116 return true; 117 } 118 }; 119 120 /** 121 * The {@link OnQueryTextListener} that is called when text in the 122 * {@link SearchView} is changed. Updates the query filter and triggers a 123 * reload of the {@link LoaderManager} to update the displayed list. 124 */ 125 private OnQueryTextListener queryListener = new OnQueryTextListener() { 126 127 /** 128 * Called when the action bar search text has changed. Update the search 129 * filter, and restart the loader to do a new query with this filter. 130 * 131 * @param newText the new content of the query text field 132 * @return true, the action has been handled. 133 */ 134 public boolean onQueryTextChange(String newText) { 135 136 String newFilter = !TextUtils.isEmpty(newText) ? newText : null; 137 138 // Don't do anything if the filter hasn't actually changed. 139 // Prevents restarting the loader when restoring state. 140 if (mCurFilter == null && newFilter == null) { 141 return true; 142 } 143 if (mCurFilter != null && mCurFilter.equals(newFilter)) { 144 return true; 145 } 146 147 // Restart the Loader. 148 // #onCreateLoader uses the value of mCurFilter as a filter when 149 // creating the query for the Loader. 150 mCurFilter = newFilter; 151 getLoaderManager().restartLoader(0, null, CursorLoaderListFragment.this); 152 153 return true; 154 } 155 156 @Override 157 public boolean onQueryTextSubmit(String query) { 158 // Don't care about this. 159 return true; 160 } 161 }; 162 163 /** 164 * An item has been clicked in the {@link ListView}. Display a toast with 165 * the tapped item's id. 166 */ 167 @Override onListItemClick(ListView l, View v, int position, long id)168 public void onListItemClick(ListView l, View v, int position, long id) { 169 Toast.makeText(getActivity(), "Item clicked: " + id, Toast.LENGTH_LONG).show(); 170 } 171 172 // These are the Contacts rows that we will retrieve. 173 static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { 174 Contacts._ID, 175 Contacts.DISPLAY_NAME, 176 Contacts.CONTACT_STATUS, 177 Contacts.LOOKUP_KEY, 178 }; 179 180 // BEGIN_INCLUDE(oncreateloader) onCreateLoader(int id, Bundle args)181 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 182 // This is called when a new Loader needs to be created. This 183 // sample only has one Loader, so we don't care about the ID. 184 // First, pick the base URI to use depending on whether we are 185 // currently filtering. 186 Uri baseUri; 187 if (mCurFilter != null) { 188 baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, 189 Uri.encode(mCurFilter)); 190 } else { 191 baseUri = Contacts.CONTENT_URI; 192 } 193 194 // Now create and return a CursorLoader that will take care of 195 // creating a Cursor for the data being displayed. 196 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" 197 + Contacts.HAS_PHONE_NUMBER + "=1) AND (" 198 + Contacts.DISPLAY_NAME + " != '' ))"; 199 String order = Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; 200 201 return new CursorLoader(getActivity(), baseUri, 202 CONTACTS_SUMMARY_PROJECTION, select, null, order); 203 } 204 205 // END_INCLUDE(oncreateloader) 206 207 // BEGIN_INCLUDE(onloadfinished) onLoadFinished(Loader<Cursor> loader, Cursor data)208 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 209 // Swap the new cursor in. (The framework will take care of closing the 210 // old cursor once we return.) 211 mAdapter.swapCursor(data); 212 213 // The list should now be shown. 214 if (isResumed()) { 215 setListShown(true); 216 } else { 217 setListShownNoAnimation(true); 218 } 219 } 220 221 // END_INCLUDE(onloadfinished) 222 223 // BEGIN_INCLUDE(onloaderreset) onLoaderReset(Loader<Cursor> loader)224 public void onLoaderReset(Loader<Cursor> loader) { 225 // This is called when the last Cursor provided to onLoadFinished() 226 // above is about to be closed. We need to make sure we are no 227 // longer using it. 228 mAdapter.swapCursor(null); 229 } 230 // END_INCLUDE(onloaderreset) 231 232 } 233