Book Excerpt: Full-Text Indexing and Searching (Part 3)
The following sections are excerpts from Version 6.9 of “The Busy Coder’s Guide to Android Development”, with slight modifications to fit the blog format. It continues the blog post series begun Monday.
As noted previously, this sample app is a revised version of the Stack Overflow questions list from the chapter on Internet access. It is specifically derived from the Picasso version of the sample. However, this version is designed to allow the user to full-text search the downloaded question data (e.g., title), above and beyond just seeing the list of latest questions.
The original sample had a very simple data model: a list of questions retrieved via Retrofit. Hence, the sample did not include much in the way of model management.
The FTS sample needs a database, which implies more local disk I/O that
we are responsible for, which in turn leads us in the direction of implementing
a model fragment (ModelFragment
), much as the book’s tutorials and a few other
samples do:
package com.commonsware.android.fts;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import de.greenrobot.event.EventBus;
import retrofit.RestAdapter;
public class ModelFragment extends Fragment {
private Context app=null;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setRetainInstance(true);
}
@Override
public void onAttach(Activity host) {
super.onAttach(host);
EventBus.getDefault().register(this);
if (app==null) {
app=host.getApplicationContext();
new FetchQuestionsThread().start();
}
}
@Override
public void onDetach() {
EventBus.getDefault().unregister(this);
super.onDetach();
}
public void onEventBackgroundThread(SearchRequestedEvent event) {
try {
Cursor results=DatabaseHelper.getInstance(app).loadQuestions(app, event.match);
EventBus.getDefault().postSticky(new ModelLoadedEvent(results));
}
catch (Exception e) {
Log.e(getClass().getSimpleName(),
"Exception searching database", e);
}
}
class FetchQuestionsThread extends Thread {
@Override
public void run() {
RestAdapter restAdapter=
new RestAdapter.Builder().setEndpoint("https://api.stackexchange.com")
.build();
StackOverflowInterface so=
restAdapter.create(StackOverflowInterface.class);
SOQuestions questions=so.questions("android");
try {
DatabaseHelper
.getInstance(app)
.insertQuestions(app, questions.items);
}
catch (Exception e) {
Log.e(getClass().getSimpleName(),
"Exception populating database", e);
}
try {
Cursor results=DatabaseHelper.getInstance(app).loadQuestions(app, null);
EventBus.getDefault().postSticky(new ModelLoadedEvent(results));
}
catch (Exception e) {
Log.e(getClass().getSimpleName(),
"Exception populating database", e);
}
}
}
}
In onCreate()
, we mark this fragment as retained, as that is key to the
model fragment pattern, so the fragment retains the model data across
configuration changes.
In onAttach()
, we register for the greenrobot EventBus, plus kick off
a FetchQuestionsThread
if we have not done so already (i.e., this is
the first onAttach()
call we have received). onDetach()
unregisters
us from the event bus.
FetchQuestionsThread
, in turn, uses Retrofit to download the questions
from Stack Overflow, then uses DatabaseHelper
to insert the questions
into the FTS-enabled database table, then uses the DatabaseHelper
again
to retrieve all existing questions in the form of a Cursor
, which it
wraps in a ModelLoadedEvent
and posts to the EventBus
. This time, though,
it posts it as a sticky event.
That sticky event is consumed by a revised version of the QuestionsFragment
,
in its onEventMainThread()
method:
public void onEventMainThread(ModelLoadedEvent event) {
((SimpleCursorAdapter)getListAdapter()).changeCursor(event.model);
if (sv!=null) {
sv.setEnabled(true);
}
}
Here, sv
is a SearchView
; we will look more at it tomorrow.
But because this is a sticky event, we will get this event both when it is
raised (because the data is loaded) and any time thereafter when the fragment
registers with the EventBus
. This allows QuestionsFragment
to not be
retained, as it will get back the bulk of its model data automatically from
greenrobot’s EventBus.
QuestionsFragment
also is modified from the Picasso sample to deal with the
fact that its model data is now a Cursor
, so it uses SimpleCursorAdapter
to populate the list. To handle loading avatar images from the URLs,
QuestionsFragment
adds a QuestionBinder
implementation of ViewBinder
to the SimpleCursorAdapter
, where QuestionBinder
handles the Picasso logic
from before:
private class QuestionBinder implements SimpleCursorAdapter.ViewBinder {
int size;
QuestionBinder() {
size=getActivity()
.getResources()
.getDimensionPixelSize(R.dimen.icon);
}
@Override
public boolean setViewValue (View view, Cursor cursor, int columnIndex) {
switch (view.getId()) {
case R.id.title:
((TextView)view).setText(Html.fromHtml(cursor.getString(columnIndex)));
return(true);
case R.id.icon:
Picasso.with(getActivity()).load(cursor.getString(columnIndex))
.resize(size, size).centerCrop()
.placeholder(R.drawable.owner_placeholder)
.error(R.drawable.owner_error).into((ImageView)view);
return(true);
}
return(false);
}
}
The main activity (MainActivity
) sets up the ModelFragment
in onCreate()
,
at least when one does not already exist due to a configuration change:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
getFragmentManager().beginTransaction()
.add(android.R.id.content,
new QuestionsFragment()).commit();
}
model=(ModelFragment)getFragmentManager().findFragmentByTag(MODEL);
if (model==null) {
model=new ModelFragment();
getFragmentManager().beginTransaction().add(model, MODEL).commit();
}
}
This description, though, has skipped over the onEventBackgroundThread()
method on the ModelFragment
, which we will get to later in this blog post
series.
In
tomorrow’s post,
we will look at how the SearchView
is integrated,
to perform the full-text search and show the results.