: Beginning Android

Craft the Search Activity

Craft the Search Activity

The first thing youll want to do if you want to support query-style search in your application is to create a search activity. While it might be possible to have a single activity be both opened from the launcher and opened from a search, that might prove somewhat confusing to users. Certainly, for the purposes of learning the techniques, having a separate activity is cleaner.

The search activity can have any look you want. In fact, other than watching for queries, a search activity looks, walks, and talks like any other activity in your system.

All the search activity needs to do differently is check the intents supplied to onCreate() (via getIntent()) and onNewIntent() to see if one is a search, and, if so, to do the search and display the results.

For example, lets look at the Search/Lorem sample application (available in the Source Code section of http://apress.com). This starts off as a clone of the list-of-lorem-ipsum-words application that we first built back when showing off the ListView container in Chapter 8, then with XML resources in Chapter 19. Now we update it to support searching the list of words for ones containing the search string.

The main activity and the search activity share a common layout: a ListView plus a TextView showing the selected entry:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>

In terms of Java code, most of the guts of the activities are poured into an abstract LoremBase class:

abstractpublicclass LoremBaseextends ListActivity {
abstract ListAdaptermakeMeAnAdapter(Intent intent);
privatestaticfinal int LOCAL_SEARCH_ID = Menu.FIRST+1;
privatestaticfinal int GLOBAL_SEARCH_ID = Menu.FIRST+2;
privatestaticfinal int CLOSE_ID = Menu.FIRST+3;
TextView selection;
ArrayList<String> items = new ArrayList<String>();
@Override
public voidonCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection = (TextView)findViewById(R.id.selection);
try {
XmlPullParser xpp =getResources().getXml(R.xml.words);
while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) {
if (xpp.getEventType()==XmlPullParser.START_TAG) {
if (xpp.getName().equals("word")) {
items.add(xpp.getAttributeValue(0));
}
}
xpp.next();
}
}catch (Throwable t) {
Toast
.makeText(this, "Request failed: " + t.toString(), 4000).show();
}
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
onNewIntent(getIntent());
}
@Override
public voidonNewIntent(Intent intent) {
ListAdapter adapter =makeMeAnAdapter(intent);
if (adapter==null) {
finish();
}else {
setListAdapter(adapter);
}
}
public voidonListItemClick(ListView parent, View v, int position,
long id) {
selection.setText(items.get(position).toString());
}
@Override
public booleanonCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, LOCAL_SEARCH_ID, Menu.NONE, "Local Search")
.setIcon(android.R.drawable.ic_search_category_default);
menu.add(Menu.NONE, GLOBAL_SEARCH_ID, Menu.NONE, "Global Search")
.setIcon(R.drawable.search).setAlphabeticShortcut(SearchManager.MENU_KEY);
menu.add(Menu.NONE, CLOSE_ID, Menu.NONE, "Close")
.setIcon(R.drawable.eject).setAlphabeticShortcut('c');
return(super.onCreateOptionsMenu(menu));
}
@Override
public booleanonOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case LOCAL_SEARCH_ID:
onSearchRequested();
return(true);
case GLOBAL_SEARCH_ID:
startSearch(null,false,null,true);
return(true);
case CLOSE_ID:
finish();
return(true);
}
return(super.onOptionsItemSelected(item));
}
}

This activity takes care of everything related to showing a list of words, even loading the words out of the XML resource. What it does not do is come up with the ListAdapter to put into the ListView that is delegated to the subclasses.

The main activity LoremDemo just uses a ListAdapter for the whole word list:

package com.commonsware.android.search;
import android.content.Intent;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
publicclass LoremDemoextends LoremBase {
@Override ListAdaptermakeMeAnAdapter(Intent intent) {
return(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items));
}
}

The search activity, though, does things a bit differently. First, it inspects the Intent supplied to the abstract makeMeAnAdapter() method. That Intent comes from either onCreate() or onNewIntent(). If the intent is an ACTION_SEARCH, then we know this is a search. We can get the search query and, in the case of this silly demo, spin through the loaded list of words and find only those containing the search string. That list then gets wrapped in a ListAdapter and returned for display:

package com.commonsware.android.search;
import android.app.SearchManager;
import android.content.Intent;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import java.util.ArrayList;
import java.util.List;
publicclass LoremSearchextends LoremBase {
@Override
ListAdaptermakeMeAnAdapter(Intent intent) {
ListAdapter adapter =null;
if (intent.getAction().equals(Intent.ACTION_SEARCH)) {
String query = intent.getStringExtra(SearchManager.QUERY);
List<String> results =searchItems(query);
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, results);
setTitle("LoremSearch for: " + query);
}
return(adapter);
}
private List<String> searchItems(String query) {
List<String> results = new ArrayList<String>();
for (String item : items) {
if (item.indexOf(query) - 1) {
results.add(item);
}
}
return(results);
}
}


: 1.091. /Cache: 3 / 1