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 <div id="tb-wrapper"> <div id="tb"> <h2>This lesson teaches you to</h2> <ul> <li><a href="{@docRoot}training/search/search.html#create">Create the Virtual Table</a></li> <li><a href="{@docRoot}training/search/search.html#populate">Populate the Virtual Table</a></li> <li><a href="{@docRoot}training/search/search.html#search">Search for the Query</a></li> </ul> </div> </div> <p>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.</p> <h2 id="create">Create the Virtual Table</h2> <p>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:</p> <pre> public class DatabaseTable { private final DatabaseOpenHelper mDatabaseOpenHelper; public DatabaseTable(Context context) { mDatabaseOpenHelper = new DatabaseOpenHelper(context); } } </pre> <p>Create an inner class in <code>DatabaseTable</code> 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:</p> <pre> 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); } } } </pre> <h2 id="populate">Populate the Virtual Table</h2> <p>The table now needs data to store. The following code shows you how to read a text file (located in <code>res/raw/definitions.txt</code>) 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 <code>DatabaseOpenHelper</code> inner class.</p> <p class="note"><strong>Tip:</strong> You also might want to set up a callback to notify your UI activity of this thread's completion.</p> <pre> 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); } </pre> <p>Call the <code>loadDictionary()</code> method wherever appropriate to populate the table. A good place would be in the {@link android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()} method of the <code>DatabaseOpenHelper</code> class, right after you create the table:</p> <pre> @Override public void onCreate(SQLiteDatabase db) { mDatabase = db; mDatabase.execSQL(FTS_TABLE_CREATE); loadDictionary(); } </pre> <h2 id="search">Search for the Query</h2> <p>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 <code>DatabaseTable</code> class to build a SQL statement that searches for the query:</p> <pre> 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; } </pre> <p>Search for a query by calling <code>getWordMatches()</code>. 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 <code>getWordMatches()</code> in the <code>handleIntent()</code> 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:</p> <pre> 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 } } </pre>