summaryrefslogtreecommitdiffstats
path: root/docs/html/training/search/search.jd
blob: 17e7640cf7a1a87aebe1282e621b0116805a29c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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

  <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;
        }

        &#64;Override
        public void onCreate(SQLiteDatabase db) {
            mDatabase = db;
            mDatabase.execSQL(FTS_TABLE_CREATE);
        }

        &#64;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 &lt; 2) continue;
            long id = addWord(strings[0].trim(), strings[1].trim());
            if (id &lt; 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>
&#64;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>