diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index 848303751a1c9..890f92a42a1f0 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -98,6 +98,26 @@ +
  • +
    + Adding Search Functionality +
    + +
  • +
  • Remembering Users diff --git a/docs/html/training/search/backward-compat.jd b/docs/html/training/search/backward-compat.jd new file mode 100644 index 0000000000000..de0e3d233f0bf --- /dev/null +++ b/docs/html/training/search/backward-compat.jd @@ -0,0 +1,87 @@ +page.title=Remaining Backward Compatible +trainingnavtop=true +previous.title=Storing and Searching for Data +previous.link=search.html + +@jd:body + + + +

    The {@link android.widget.SearchView} and action bar are only available on Android 3.0 and + later. To support older platforms, you can fall back to the search dialog. The search dialog is a + system provided UI that overlays on top of your application when invoked.

    + +

    Set Minimum and Target API levels

    + +

    To setup the search dialog, first declare in your manifest that you want to support older + devices, but want to target Android 3.0 or later versions. When you do this, your application + automatically uses the action bar on Android 3.0 or later and uses the traditional menu system on + older devices:

    +
    +<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15" />
    +
    +<application>
    +...
    +
    + +

    Provide the Search Dialog for Older Devices

    + +

    To invoke the search dialog on older devices, call {@link + android.app.Activity#onSearchRequested onSearchRequested()} whenever a user selects the search + menu item from the options menu. Because Android 3.0 and higher devices show the + {@link android.widget.SearchView} in the action bar (as demonstrated in the first lesson), only versions + older than 3.0 call {@link android.app.Activity#onOptionsItemSelected onOptionsItemSelected()} when the + user selects the search menu item. +

    +
    +@Override
    +public boolean onOptionsItemSelected(MenuItem item) {
    +    switch (item.getItemId()) {
    +        case R.id.search:
    +            onSearchRequested();
    +            return true;
    +        default:
    +            return false;
    +    }
    +}
    +
    + +

    Check the Android Build Version at Runtime

    + +

    At runtime, check the device version to make sure an unsupported use of {@link + android.widget.SearchView} does not occur on older devices. In our example code, this happens in + the {@link android.app.Activity#onCreateOptionsMenu onCreateOptionsMenu()} method:

    +
    +@Override
    +public boolean onCreateOptionsMenu(Menu menu) {
    +
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.options_menu, menu);
    +
    +    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    +        SearchManager searchManager =
    +                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    +        SearchView searchView =
    +                (SearchView) menu.findItem(R.id.search).getActionView();
    +        searchView.setSearchableInfo(
    +                searchManager.getSearchableInfo(getComponentName()));
    +        searchView.setIconifiedByDefault(false);
    +    }
    +    return true;
    +}
    +
    diff --git a/docs/html/training/search/index.jd b/docs/html/training/search/index.jd new file mode 100644 index 0000000000000..bfd16187c4b3a --- /dev/null +++ b/docs/html/training/search/index.jd @@ -0,0 +1,53 @@ +page.title=Adding Search Functionality +trainingnavtop=true +startpage=true +next.title=Setting Up the Search Interface +next.link=setup.html + +@jd:body + +
    +
    +

    Dependencies and prerequisites

    + +
      +
    • Android 3.0 or later (with some support for Android 2.1)
    • + +
    • Experience building an Android User + Interface
    • +
    + +

    You should also read

    + + +
    +
    + +

    Android's built-in search features offer apps an easy way to provide a + consistent search experience for all users. There are two ways to implement search in your app + depending on the version of Android that is running on the device. This class covers how to add + search with {@link android.widget.SearchView}, which was introduced in Android 3.0, while + maintaining backward compatibility with older versions of Android by using the default search + dialog provided by the system.

    + +

    Lessons

    + +
    +
    Setting Up the Search Interface
    + +
    Learn how to add a search interface to your app and how to configure an activity to handle + search queries.
    + +
    Storing and Searching for Data
    + +
    Learn a simple way to store and search for data in a SQLite virtual database table.
    + +
    Remaining Backward Compatible
    + +
    Learn how to keep search features backward compatible with older devices by using.
    +
    \ No newline at end of file diff --git a/docs/html/training/search/search.jd b/docs/html/training/search/search.jd new file mode 100644 index 0000000000000..17e7640cf7a1a --- /dev/null +++ b/docs/html/training/search/search.jd @@ -0,0 +1,217 @@ +page.title=Storing and Searching for Data +trainingnavtop=true +previous.title=Setting Up the Search Interface +previous.link=setup.html +next.title=Remaining Backward Compatible +next.link=backward-compat.html + +@jd:body + +
    +
    +

    This lesson teaches you to

    + + +
    +
    + +

    There are many ways to store your data, such as in an online database, in a local SQLite + database, or even in a text file. It is up to you to decide what is the best solution for your + application. This lesson shows you how to create a SQLite virtual table that can provide robust + full-text searching. The table is populated with data from a text file that contains a word and + definition pair on each line in the file.

    + +

    Create the Virtual Table

    + +

    A virtual table behaves similarly to a SQLite table, but reads and writes to an object in + memory via callbacks, instead of to a database file. To create a virtual table, create a class + for the table:

    +
    +public class DatabaseTable {
    +    private final DatabaseOpenHelper mDatabaseOpenHelper;
    +
    +    public DatabaseTable(Context context) {
    +        mDatabaseOpenHelper = new DatabaseOpenHelper(context);
    +    }
    +}
    +
    + +

    Create an inner class in DatabaseTable that extends {@link + android.database.sqlite.SQLiteOpenHelper}. The {@link android.database.sqlite.SQLiteOpenHelper} class + defines abstract methods that you must override so that your database table can be created and + upgraded when necessary. For example, here is some code that declares a database table that will + contain words for a dictionary app:

    +
    +public class DatabaseTable {
    +
    +    private static final String TAG = "DictionaryDatabase";
    +
    +    //The columns we'll include in the dictionary table
    +    public static final String COL_WORD = "WORD";
    +    public static final String COL_DEFINITION = "DEFINITION";
    +
    +    private static final String DATABASE_NAME = "DICTIONARY";
    +    private static final String FTS_VIRTUAL_TABLE = "FTS";
    +    private static final int DATABASE_VERSION = 1;
    +
    +    private final DatabaseOpenHelper mDatabaseOpenHelper;
    +
    +    public DatabaseTable(Context context) {
    +        mDatabaseOpenHelper = new DatabaseOpenHelper(context);
    +    }
    +
    +    private static class DatabaseOpenHelper extends SQLiteOpenHelper {
    +
    +        private final Context mHelperContext;
    +        private SQLiteDatabase mDatabase;
    +
    +        private static final String FTS_TABLE_CREATE =
    +                    "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
    +                    " USING fts3 (" +
    +                    COL_WORD + ", " +
    +                    COL_DEFINITION + ")";
    +
    +        DatabaseOpenHelper(Context context) {
    +            super(context, DATABASE_NAME, null, DATABASE_VERSION);
    +            mHelperContext = context;
    +        }
    +
    +        @Override
    +        public void onCreate(SQLiteDatabase db) {
    +            mDatabase = db;
    +            mDatabase.execSQL(FTS_TABLE_CREATE);
    +        }
    +
    +        @Override
    +        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    +            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
    +                    + newVersion + ", which will destroy all old data");
    +            db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
    +            onCreate(db);
    +        }
    +    }
    +}
    +
    + +

    Populate the Virtual Table

    + +

    The table now needs data to store. The following code shows you how to read a text file + (located in res/raw/definitions.txt) that contains words and their definitions, how + to parse that file, and how to insert each line of that file as a row in the virtual table. This + is all done in another thread to prevent the UI from locking. Add the following code to your + DatabaseOpenHelper inner class.

    + +

    Tip: You also might want to set up a callback to notify your UI + activity of this thread's completion.

    +
    +private void loadDictionary() {
    +        new Thread(new Runnable() {
    +            public void run() {
    +                try {
    +                    loadWords();
    +                } catch (IOException e) {
    +                    throw new RuntimeException(e);
    +                }
    +            }
    +        }).start();
    +    }
    +
    +private void loadWords() throws IOException {
    +    final Resources resources = mHelperContext.getResources();
    +    InputStream inputStream = resources.openRawResource(R.raw.definitions);
    +    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    +
    +    try {
    +        String line;
    +        while ((line = reader.readLine()) != null) {
    +            String[] strings = TextUtils.split(line, "-");
    +            if (strings.length < 2) continue;
    +            long id = addWord(strings[0].trim(), strings[1].trim());
    +            if (id < 0) {
    +                Log.e(TAG, "unable to add word: " + strings[0].trim());
    +            }
    +        }
    +    } finally {
    +        reader.close();
    +    }
    +}
    +
    +public long addWord(String word, String definition) {
    +    ContentValues initialValues = new ContentValues();
    +    initialValues.put(COL_WORD, word);
    +    initialValues.put(COL_DEFINITION, definition);
    +
    +    return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
    +}
    +
    + +

    Call the loadDictionary() method wherever appropriate to populate the table. A + good place would be in the {@link android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()} + method of the DatabaseOpenHelper class, right after you create the table:

    +
    +@Override
    +public void onCreate(SQLiteDatabase db) {
    +    mDatabase = db;
    +    mDatabase.execSQL(FTS_TABLE_CREATE);
    +    loadDictionary();
    +}
    +
    + + + +

    When you have the virtual table created and populated, use the query supplied by your {@link + android.widget.SearchView} to search the data. Add the following methods to the + DatabaseTable class to build a SQL statement that searches for the query:

    +
    +public Cursor getWordMatches(String query, String[] columns) {
    +    String selection = COL_WORD + " MATCH ?";
    +    String[] selectionArgs = new String[] {query+"*"};
    +
    +    return query(selection, selectionArgs, columns);
    +}
    +
    +private Cursor query(String selection, String[] selectionArgs, String[] columns) {
    +    SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
    +    builder.setTables(FTS_VIRTUAL_TABLE);
    +
    +    Cursor cursor = builder.query(mDatabaseOpenHelper.getReadableDatabase(),
    +            columns, selection, selectionArgs, null, null, null);
    +
    +    if (cursor == null) {
    +        return null;
    +    } else if (!cursor.moveToFirst()) {
    +        cursor.close();
    +        return null;
    +    }
    +    return cursor;
    +}
    +
    + +

    Search for a query by calling getWordMatches(). Any matching results are returned + in a {@link android.database.Cursor} that you can iterate through or use to build a {@link android.widget.ListView}. + This example calls getWordMatches() in the handleIntent() method of the searchable + activity. Remember that the searchable activity receives the query inside of the {@link + android.content.Intent#ACTION_SEARCH} intent as an extra, because of the intent filter that you + previously created:

    +
    +DatabaseTable db = new DatabaseTable(this);
    +
    +...
    +
    +private void handleIntent(Intent intent) {
    +
    +    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
    +        String query = intent.getStringExtra(SearchManager.QUERY);
    +        Cursor c = db.getWordMatches(query, null);
    +        //process Cursor and display results
    +    }
    +}
    +
    \ No newline at end of file diff --git a/docs/html/training/search/setup.jd b/docs/html/training/search/setup.jd new file mode 100644 index 0000000000000..044e422fe6a6a --- /dev/null +++ b/docs/html/training/search/setup.jd @@ -0,0 +1,197 @@ +page.title=Setting Up the Search Interface +trainingnavtop=true +next.title=Storing and Searching for Data +next.link=search.html + +@jd:body + +
    +
    +

    This lesson teaches you to

    + + + +

    You should also read:

    + + +
    +
    + +

    Beginning in Android 3.0, using the {@link android.widget.SearchView} widget as an item in + the action bar is the preferred way to provide search in your app. Like with all items in + the action bar, you can define the {@link android.widget.SearchView} to show at all times, only + when there is room, or as a collapsible action, which displays the {@link + android.widget.SearchView} as an icon initially, then takes up the entire action bar as a search + field when the user clicks the icon.

    + +

    Note: Later in this class, you will learn how to make your + app compatible down to Android 2.1 (API level 7) for devices that do not support + {@link android.widget.SearchView}.

    + +

    Add the Search View to the Action Bar

    + +

    To add a {@link android.widget.SearchView} widget to the action bar, create a file named + res/menu/options_menu.xml in your project and add the following code to the file. + This code defines how to create the search item, such as the icon to use and the title of the + item. The collapseActionView attribute allows your {@link android.widget.SearchView} + to expand to take up the whole action bar and collapse back down into a + normal action bar item when not in use. Because of the limited action bar space on handset devices, + using the collapsibleActionView attribute is recommended to provide a better + user experience.

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<menu xmlns:android="http://schemas.android.com/apk/res/android">
    +    <item android:id="@+id/search"
    +          android:title="@string/search_title"
    +          android:icon="@drawable/ic_search"
    +          android:showAsAction="collapseActionView|ifRoom"
    +          android:actionViewClass="android.widget.SearchView" />
    +</menu>
    +
    + +

    Note: If you already have an existing XML file for your menu + items, you can add the <item> element to that file instead.

    + +

    To display the {@link android.widget.SearchView} in the action bar, inflate the XML menu + resource (res/menu/options_menu.xml) in the {@link + android.app.Activity#onCreateOptionsMenu onCreateOptionsMenu()} method of your activity:

    +
    +@Override
    +public boolean onCreateOptionsMenu(Menu menu) {
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.options_menu, menu);
    +
    +    return true;
    +}
    +
    + +

    If you run your app now, the {@link android.widget.SearchView} appears in your app's action + bar, but it isn't functional. You now need to define how the {@link + android.widget.SearchView} behaves.

    + +

    Create a Searchable Configuration

    + +

    A searchable + configuration defines how the {@link android.widget.SearchView} behaves and is defined in a + res/xml/searchable.xml file. At a minimum, a searchable configuration must contain + an android:label attribute that has the same value as the + android:label attribute of the <application> or + <activity> element in your Android manifest. + However, we also recommend adding an android:hint attribute to give the user an idea of what to enter into the search + box:

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +
    +<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    +        android:label="@string/app_name"
    +        android:hint="@string/search_hint" />
    +
    + +

    In your application's manifest file, declare a + <meta-data> element that points to the res/xml/searchable.xml file, + so that your application knows where to find it. Declare the element in an <activity> + that you want to display the {@link android.widget.SearchView} in:

    +
    +<activity ... >
    +    ...
    +    <meta-data android:name="android.app.searchable"
    +            android:resource="@xml/searchable" />
    +
    +</activity>
    +
    + +

    In the {@link android.app.Activity#onCreateOptionsMenu onCreateOptionsMenu()} method that you + created before, associate the searchable configuration with the {@link android.widget.SearchView} + by calling {@link android.widget.SearchView#setSearchableInfo}:

    +
    +@Override
    +public boolean onCreateOptionsMenu(Menu menu) {
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.options_menu, menu);
    +
    +    // Associate searchable configuration with the SearchView
    +    SearchManager searchManager =
    +           (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    +    SearchView searchView =
    +            (SearchView) menu.findItem(R.id.search).getActionView();
    +    searchView.setSearchableInfo(
    +            searchManager.getSearchableInfo(getComponentName()));
    +
    +    return true;
    +}
    +
    + +

    The call to {@link android.app.SearchManager#getSearchableInfo getSearchableInfo()} obtains a + {@link android.app.SearchableInfo} object that is created from the searchable configuration XML + file. When the searchable configuration is correctly associated with your {@link + android.widget.SearchView}, the {@link android.widget.SearchView} starts an activity with the + {@link android.content.Intent#ACTION_SEARCH} intent when a user submits a query. You now need an + activity that can filter for this intent and handle the search query.

    + +

    Create a Searchable Activity

    + +

    A {@link android.widget.SearchView} tries to start an activity with the {@link + android.content.Intent#ACTION_SEARCH} when a user submits a search query. A searchable activity + filters for the {@link android.content.Intent#ACTION_SEARCH} intent and searches for the query in + some sort of data set. To create a searchable activity, declare an activity of your choice to + filter for the {@link android.content.Intent#ACTION_SEARCH} intent:

    +
    +<activity android:name=".SearchResultsActivity" ... >
    +    ...
    +    <intent-filter>
    +        <action android:name="android.intent.action.SEARCH" />
    +    </intent-filter>
    +    ...
    +</activity>
    +
    + +

    In your searchable activity, handle the {@link android.content.Intent#ACTION_SEARCH} intent by + checking for it in your {@link android.app.Activity#onCreate onCreate()} method.

    + +

    Note: If your searchable activity launches in single top mode + (android:launchMode="singleTop"), also handle the {@link + android.content.Intent#ACTION_SEARCH} intent in the {@link android.app.Activity#onNewIntent + onNewIntent()} method. In single top mode, only one instance of your activity is created and + subsequent calls to start your activity do not create a new activity on the + stack. This launch mode is useful so users can perform searches from the same activity + without creating a new activity instance every time.

    +
    +public class SearchResultsActivity extends Activity {
    +
    +    @Override
    +    public void onCreate(Bundle savedInstanceState) {
    +        ...
    +        handleIntent(getIntent());
    +    }
    +
    +    @Override
    +    protected void onNewIntent(Intent intent) {
    +        ...
    +        handleIntent(intent);
    +    }
    +
    +    private void handleIntent(Intent intent) {
    +
    +        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
    +            String query = intent.getStringExtra(SearchManager.QUERY);
    +            //use the query to search your data somehow
    +        }
    +    }
    +    ...
    +}
    +
    + +

    If you run your app now, the {@link android.widget.SearchView} can accept the user's query and + start your searchable activity with the {@link android.content.Intent#ACTION_SEARCH} intent. It + is now up to you to figure out how to store and search your data given a query.

    \ No newline at end of file