Book Excerpt: Full-Text Indexing and Searching (Part 4)

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 is covered in the chapter on advanced action bar techniques, a SearchView can be used to provide the standard “magnifying glass” search icon in the action bar. When tapped, the action bar item expands into a field where the user can type something, which our code can then receive and use to update the UI. In the SearchView sample from the action bar chapter, we saw using a SearchView for filtering. This time, we will use a SearchView for searching.

For a search, we need to know when the user is done typing, which is usually done by the user clicking a submit button. Hence, our code to configure the SearchView (a configureSearchView() method in QuestionsFragment) calls setSubmitButtonEnabled(true):

private void configureSearchView(Menu menu) {
  MenuItem search=menu.findItem(R.id.search);

  search.setOnActionExpandListener(this);
  sv=(SearchView)search.getActionView();
  sv.setOnQueryTextListener(this);
  sv.setSubmitButtonEnabled(true);
  sv.setIconifiedByDefault(true);

  if (initialQuery != null) {
    sv.setIconified(false);
    search.expandActionView();
    sv.setQuery(initialQuery, true);
  }
}

This, in turn, means that we need to pay attention to onQueryTextSubmit() in our SearchView.OnQueryTextListener implementation. That interface is implemented on QuestionsFragment itself, and delegates its work to a doSearch() method:

@Override
public boolean onQueryTextSubmit(String query) {
  doSearch(query);

  return(true);
}

That method, in turn, confirms that the search is different than the last one we did (so we do not waste time running the search again), disables the SearchView, and posts a SearchRequestedEvent on the EventBus with the user’s search string:

private void doSearch(String match) {
  if (!match.equals(lastQuery)) {
    lastQuery=match;

    if (sv != null) {
      sv.setEnabled(false);
    }

    EventBus.getDefault().post(new SearchRequestedEvent(match));
  }
}

That event is picked up by onEventBackgroundThread() on ModelFragment. The method name onEventBackgroundThread() means that the event will be delivered to us on an EventBus-supplied background thread, so we can perform database I/O. In it, we call loadQuestions() on the DatabaseHelper to perform the search, and post another sticky ModelLoadedEvent to update the UI with the search results and re-enable the SearchView:

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

When the user clears the SearchView, such as by pressing the BACK button a few times, the onMenuItemActionCollapse() method of QuestionsFragment calls a clearSearch() method:

@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
  clearSearch();

  return(true);
}

That clearSearch() method simply posts another SearchRequestedEvent, this time to load a fresh roster of all questions:

private void clearSearch() {
  if (lastQuery!=null) {
    lastQuery=null;

    sv.setEnabled(false);
    EventBus.getDefault().post(new SearchRequestedEvent(null));
  }
}

In tomorrow’s post, we will look at how you can use SQLite’s FTS tables to get “snippets” back, to help provide some context around your search results.