Android User Interface Development: Validating and Handling Input Data

Exclusive offer: get 50% off this eBook here
Android User Interface Development: Beginner's Guide

Android User Interface Development: Beginner's Guide — Save 50%

Quickly design and develop compelling user interfaces for your Android applications with this book and eBook

$26.99    $13.50
by Jason Morris | July 2011 | Beginner's Guides Web Services

Android provides an excellent toolset to capture many different types of data from the user, while also providing loose coupling between your application components in the form of Intent structures. By using several smaller Activity classes to capture data, while at the same time abstracting the functionality to capture different types of input, you'll be able to more easily reuse the input capturing Activity classes, not just within the application, but in other applications as well. Further, by registering the Activity correctly, you'll allow other applications to override, or make use of your Activity implementation, allowing the users to select their preferred capturing mechanism.

This article by Jason Morris, author of Android User Interface Development: Beginner's Guide, provides tips regarding taking input from a user, and how to keep this experience as painless as possible. This article investigates the different input widgets Android provides and how to configure them best, depending on the situation. Also, when everything else fails, how best to inform your users that what they are doing is wrong.

 

Android User Interface Development: Beginner's Guide

Android User Interface Development: Beginner's Guide

Quickly design and develop compelling user interfaces for your Android applications

        Read more about this book      

(For more resources related to this subject, see here.)

Dealing with undesirable input

Often applications require specific types of input from their users. An application captures input from its user in order for the user to tell it something about the world. This could be anything, from what the user is looking for (that is, a search term), to something about the users themselves (that is, their age). In most of these cases, the users can be guided in the way they give the input using mechanisms, such as an auto-completion box. However, if a user can give you "undesirable" input, then somewhere along the line one of them will.

Undesirable input can be anything ranging from text where a number is expected, through to a search term that yields no results. In both cases, you need to do three things:

  1. Inform the user about the format you expect the data to be in
  2. Let them know that they entered undesirable data
  3. Let them re-enter the data

Correctly labeling input

Your first defense against undesirable input from your users is to correctly label of an input widget. This doesn't just mean, having a label that reads as follows:

Date of Birth (dd/mm/yy):

It means using the correct widget to capture the data. Your input widgets are a form of a label, they indicate to the user what sort of data you expect them to enter. In many cases, they can be used to stop the user from entering invalid data, or at least make it less likely.

Keep in mind the way that users expect things to work, and that they expect to be able to select things quickly. If you need them to give your application the name of a country, don't use a Spinner and force them to scroll through a seemingly endless list of names.

Signaling undesirable input

If the user does enter something unwanted or useless, you need to tell them, and fast! The sooner you let the user know that they've given you something useless, the sooner they can correct it and get back to using your application.

A common mistake is to simply Toast the user when they press a Save or Submit button. While this is okay if you can only determine their mistake at that point, but you can almost always figure it out beforehand.

Bear in mind that on a touchscreen device, while you have a "focused" widget, it doesn't play the same role as on a desktop system, and the user isn't going to "tab" off the widget. This means that as far as possible, your user interface should respond live to the user's actions, not wait for them to do something else (that is, select another widget) before giving them some feedback. If they do something that makes another form element invalid to use, disable it. If they do something that makes a group of widgets invalid, hide the entire group from them or put it on a different screen.

Android User Interface Development: Validating and Handling Input Data

Coloring and icons are both great ways to quickly tell the user they've got something wrong. You can take the additional step of disabling any sort of Save, Next, or Submit button when you realize that some of the user's input is wrong. However, if you do disable such a button, ensure that it is clear which form element has undesirable data on it, and make sure it is on their screen. A great alternative is to Toast the user when they select a Next button, and scroll to the invalid element.

Make use of background (or asynchronous) messages if you need to check the users' input against some remote service. This will allow you to validate the user's content as they are using the application. It'll also allow you to signal that something is wrong without stopping them from using the rest of the form. They can always come back to the invalid field and correct it.

Recovering from undesirable input

Always ensure that fixing a mistake is as painless as possible for the user. The more work they have to do to correct a misspelled word (or similar), the more likely it is that they will stop using the application. The easiest way to recover from undesirable input (which happens to fit nicely with the above comments) is to tell the user about it before they have a chance to move to another part of the process. However, this isn't always possible.

There are times when you need to pop up a Please Wait dialog during a process that will (generally as a side effect) validate the users input. In these cases, it's wise to use a ProgressDialog so you don't move the user away from your current Activity during this phase. This will have two important side effects:

  • You don't add unnecessary layers to the activity stack
  • The input the user gave is still available when you close the ProgressDialog

Giving users direct feedback

When accepting text or other keyboard input from the users, it's best to signal its validity to the users while they are still entering it. A common method is to use an ImageView to the right of the EditText widget, and changing the image content to signal whether the user has entered valid or invalid content. The image displayed in the ImageView can be set, based on whether the input is currently valid or not. This gives the user a live view of the validation process. This mechanism also works well for signaling variable levels of validation (that is, when the input is not strictly valid or invalid, but rather good quality or undesirable quality), such as in the case of a password input.

You can either make use of image icons, or simply use an Android drawable XML resource to represent the validity (that is, green for valid, red for invalid). This also means that your icon will scale to any size that you prescribe to it in your layout XML file.

Colors and icons
It's often a good idea to use a non-color indicator to differentiate icons. Someone who is color blind may find it difficult or impossible to tell the difference between two icons unless you change the shape as well as the color. Having your "valid" icon as a green circle, and your "invalid" icon as a red hexagon will make your application more usable.

In order to avoid cluttering your screen with icons, you may want to display only the validation icon next to the field the user is currently working with. It's a good idea however, to make use of the INVISIBLE View state instead of GONE in order to avoid changing the layout when the user changes the focus of the user interface. At the same time, please ensure that validation icons are the same size.

Avoiding invalid input entirely

Remember that with a mobile device, time is often a constraint for the user. For this reason (and for simple usability reasons) you should generally strive to avoid invalid input from your users entirely. Android provides you with several mechanisms with which to do this, and it's wise to make use of them at every opportunity. Generally, you will want to make use of widgets that avoid validation requirements. This is almost always an option in Android, and even when your requirements are more complex than simple type information, you can generally customize the widget to stop the user from breaking your validation rules.

Capturing date and time

As we've already discussed, when inputting date and time you should make use of DatePicker and TimePicker widgets, or the DatePickerDialog and TimePickerDialog to avoid the layout issues that the primitive widgets introduce.

Avoid creating your own calendar widget unless it's a hard requirement of your application. You may not like how a DatePickerDialog looks, but users have seen them in other Android applications and know how to use them. It's also possible that these standard widgets are improved in future Android releases, giving your application an improvement with no work from your side.

Android User Interface Development: Validating and Handling Input Data

You may find that you need additional validation for date and time inputs, especially when capturing date or time ranges. For example, if you ask a user for a date of birth, the user shouldn't be able to enter a field that indicates any time later than "today" (unless it's an expected date of birth). While the DatePicker class has an event listener which allows you to listen for changes to its data (and DatePickerDialog implements this event listener), you cannot use this event listener to cancel the change event.

Therefore, in order to Cancel the event, you need to change the input back to something valid while the event is executing. This is a surprisingly simple trick in Android. Since the events are executed on the same thread that does the painting, it allows you to change the value before the invalid data is rendered on the screen. The following is a simple example of a ValidatingDatePickerDialog which you can use in order to implement a simple level of date validation in your application. Another such class could be easily written for TimePickerDialog if you needed one.

public class ValidatingDatePickerDialog extends DatePickerDialog {
private int lastValidYear;
private int lastValidMonth;
private int lastValidDay;
private ValidationCallback callback = null;
public ValidatingDatePickerDialog(
final Context context,
final OnDateSetListener callBack,
final int year,
final int monthOfYear,
final int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
setValidData(year, monthOfYear, dayOfMonth);
}
protected void setValidData(
final int year,
final int monthOfYear,
final int dayOfMonth) {
lastValidYear = year;
lastValidMonth = monthOfYear;
lastValidDay = dayOfMonth;
}
@Override
public void onDateChanged(
final DatePicker view,
final int year,
final int month,
final int day) {
if(callback != null && !callback.isValid(year, month, day)) {
view.updateDate(
lastValidYear,
lastValidMonth,
lastValidDay);
} else {
super.onDateChanged(view, year, month, day);
setValidData(year, month, day);
}
}
public void setValidationCallback(
final ValidationCallback callback) {
this.callback = callback;
}
public ValidationCallback getValidationCallback() {
return callback;
}
public interface ValidationCallback {
boolean isValid(int year, int monthOfYear, int dayOfMonth);
}
}

This method of handling validation can be used in most Android widgets that don't offer implicit validation of their events, and it offers a much better user experience than giving the user a Toast with the text Please enter a valid date of birth. It also avoids the need for additional layers of validation in your application.

Using spinners and ListView for selection

There are many times when the user needs to select something from a list of possible values in an application. However, they offer several features that can be very useful when it comes to validation. They are implicitly validated widgets, that is, it's impossible for the user to enter incorrect data since the possible values for input are defined by the application. However, what about when the set of valid items changes based on other user input, or some external source of information? In these cases, several options are available to you.

Changing the data set

The simplest method of stopping the user from selecting a value that is no longer valid is to remove it from the data set. Modifying the data set of an AdapterView is a good idea because it "takes the option off the menu". However, it doesn't work well with the Spinner class, since, if the item is removed off the screen, the user will be left wondering what happened to the item that was there just a second ago (and may be concerned that they are going mad).

In order not to confuse or frustrate your users, you should only remove items from a Spinner or ListView data set if the item will probably not be added back into the data set. A good example of this requirement is a list of Wi-Fi networks available, or Bluetooth devices within range. In both of these cases, the list of available items is defined by the environment. The user will accept that the displayed options are not always going to be available to them, and new items may appear from time to time.

Disabling selections

An alternative and usually more user-friendly method of stopping certain items from being selected is to disable them. You can make the ListView or Spinner ignore items by overriding the isEnabled(int) method in the ListAdapter class. However, this method will only disable the item at the event level, the item will still appear as enabled (it's primary purpose is to define separator views).

In order to visually disable an item, you'll need to disable the View that the item is displayed in. This is a very effective way of telling the user, "You've changed something that has made this item unavailable". Graphically disabling an item also lets the user know that it may become available in the future.

Capturing text input

The most difficult inputs to work with are the various forms of text input. I find that working with a soft keyboard may not be as quick as working with a hardware keyboard, but from a development point of view it offers something that a hardware keyboard does not—flexibility. When I want to enter text into a field, a soft keyboard's state will indicate the type of input that is valid for that field. If I'm supposed to enter a phone number, the keyboard can display only numbers, or even change into a dial pad. This not only indicates to me what I'm supposed to do, but also stops me from inputting anything that would cause a validation error.

The Android TextView (and thus the EditText) widgets provide you with a host of different options and methods by which you can define complex validation rules for text input. Many of these options are also understood by various soft keyboards, allowing them to display subsets of the full keyboard based on how the TextView widget has been configured. Even if not fully understood by the soft keyboard (or if a hardware keyboard is in use), the rules of the specified option must be adhered to. The easiest way to tell the EditText what type of data you want it to capture is with the inputType XML attribute.

As you'll see from the inputType documentation, all of its possible values are different combinations of the bit masks available in the android.view.inputmethod.InputType interface. The options available as values to the inputType attribute will cover most cases where you need to capture a specific type of input. You can also create your own, more complex input types by using the TextView.setRawInput or TextView. setKeyboardListener methods.

Keyboard listeners As far as possible, you should either use the input type or a standard KeyListener to handle your text validation. Writing a KeyListener is a non-trivial task, and in some cases may see you implementing a custom soft keyboard. A KeyListener in Android, which defines an input type other than TYPE_NULL, may not have its listener events (onKeyDown, onKeyUp, and onKeyOther) invoked at all if a soft keyboard is present. The key events of a KeyListener are only used to accept or reject events from a hardware keyboard. Software keyboards use the input type attribute of a TextView to decide what functionality they should provide to the user.

Autocompleting text input

The Spinner and ListView widgets are great ways to ask your user to select from a predefined list of options. However, both have a major flaw in that they don't scale well to very long lists. While the implementation and performance are both very good, users just don't like looking through massive lists of data. The standard way to solve this problem is to provide an auto completed text input widget.

Android User Interface Development: Validating and Handling Input Data

Autocompleted input widgets are also often used with a history of past options that the user has given, or to suggest possible ways the user may want to "complete" their input. The Android AutoCompleteTextView widget is an EditText with autocompletion capabilities. It uses a ListAdapter (which must also implement the Filterable interface) to find and display the list of possible suggestions to the user.

However, an AutoCompleteTextView has two major flaws:

  • It's still a TextView and the user is not forced to select one of the suggested items, this means that its content must be validated separately.
  • The suggestion list is displayed directly below the widget, consuming a fair amount of screen space. Combined with a soft keyboard for input, the user interface may become cluttered or almost unusable on a small screen

Both of these issues can be solved by using the AutoCompleteTextView class carefully and sparingly. They are brilliantly useful when you need a search box, URL input, or something similar but they are often not suitable for placing in the middle of the screen (they are best placed at the top where they have plenty of space for the suggestion list).

Android User Interface Development: Beginner's Guide Quickly design and develop compelling user interfaces for your Android applications with this book and eBook
Published: February 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

Building activities for results

There are times when none of the default widgets in Android will fulfill your input requirements on their own, and you need some sort of composite input structure. In these cases, you can either create a Dialog widget, or build a new Activity. Dialog widgets are useful when their content is kept small (two or three lines of widgets at maximum) because they visually remain on top of the current Activity. However, this means that they consume additional resources (since their calling Activity cannot be swapped out into the background), and because they have their own decorations they don't have as much available screen space to work on as an Activity.

The notion of Activity classes that hand data back to their callers is a great technique to use when you need some additional form of validation or you want to isolate a particular input widget (or group of widgets). You can specify some result data in the Activity.setResult methods. Generally, an Activity would just specify a success or failure result (using the RESULT_OK and RESULT_CANCELLED constants). It's also possible to hand back data by populating an Intent for the purpose:

Intent result = new Intent();
result.putExtra("paymentDetails", paymentDetails);
setResult(RESULT_OK, result);

The Intent data will be passed into the parent Activity object's onActivityResult method when you invoke the finish() method, along with the result code.

Generic filtering search Activity

As discussed earlier in the article, there are times where you have a predefined list of objects and you want your user to select one of them. The list is too large for the user to scroll through (for example, a list of all the countries in the world), but it's also a defined list, so you don't want them to be able to select free text.

In this case, a filterable ListView is generally the best suited option. While the ListView class has filtering capabilities, it doesn't work very well (if at all) on devices without hardware keyboards. For this reason, it's wise to make use of an EditText widget to allow the user to filter the contents of the ListView.

This sort of requirement is a very common one, and so in this section we'll look at building an Activity that is almost entirely generic in its capability to filter and select data. This example will provide two mechanisms for displaying the data to the user. One through a Cursor, and another through a simple Object array. In both cases, the task of filtering the ListView is left up to the ListAdapter implementation, keeping the implementation relatively simple.

Time for action – creating the ListItemSelectionActivity

This is a fairly large and somewhat complex example to work through, so I'll break it into bite size chunks, each with a goal. The first thing we want is an Activity class with a nice looking layout. The layout we'll build is an EditText above a ListView, each one with an ID that can be used by the Activity.

  1. Create a new project to contain your ListItemSelectionActivity class:

    android create project -n Selector -p Selector -k com.packtpub.
    selector -a ListItemSelectionActivity -t 3

  2. Open the res/layout/main.xml file in an editor or IDE.
  3. Remove any of the default layout code.
  4. Ensure that the root element is a LinearLayout consuming the available screen space in the Activity:

    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">"

  5. Inside the root element, declare an EditText with an ID of input and an inputType of textFilter to indicate that it will filter another widget's content:

    <EditText android:id="@+id/input"
    android:inputType="textFilter"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>

  6. After the EditText, we declare a ListView which consumes the remaining space:

    <ListView android:id="@+id/list"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>

  7. Open the ListItemSelectionActivity Java source file in an editor or IDE.
  8. Declare a ListAdapter field at the top of the class:

    private ListAdapter adapter;

  9. After the ListAdapter field, declare a Filter field:

    private Filter filter;

  10. In the onCreate method, make sure you are loading the main.xml as the content view for the ListItemSelectionActivity:

    setContentView(R.layout.main);

  11. Then fetch the ListView declared in the XML file for our later use:

    ListView list = (ListView)findViewById(R.id.list);

  12. Finally, fetch the EditText declared in the XML file for our later use:

    EditText input = (EditText)findViewById(R.id.input);

What just happened?

You've now got a skeleton of the ListItemSelectionActivity class. The application will be able to run at this point, presenting you with an empty ListView and an EditText. The ListAdapter and Filter fields declared at the top of the class will be used in later stages to hold the list information, and filter what is visible on the screen.

Time for action – creating an ArrayAdapter

The ListItemSelectionActivity class will accept list content from two different sources. You can either specify a database query Uri that will be used to select two columns from an external source, or you can specify an Object array as extra data in the Intent object. For the next task, we'll write a private utility method to create an ArrayAdapter from the Intent object.

  1. Open the ListItemSelectionActivity Java source file in your editor or IDE.
  2. Declare a new utility method to create a ListAdapter for Intent:

    private ListAdapter createArrayAdapter(Intent intent) {

  3. Fetch an Object array from the extra data in Intent:

    Object[] data = (Object[])intent.getSerializableExtra("data");

  4. If the array is not null, and not empty, return a new ArrayAdapter object which will display the contents of the array in the standard list item resources defined by Android:

    if(data != null && data.length > 0) {
    return new ArrayAdapter<Object>(
    this,
    android.R.layout.simple_list_item_1,
    data);

  5. If the array is either null or empty, throw an IllegalArgumentException:

    else {
    throw new IllegalArgumentException(
    "no list data specified in Intent: "
    + intent);
    }

What just happened?

You just wrote a very basic utility method to extract an Object array from an Intent, and return it. The method throws an IllegalArgumentException if the array doesn't exist, or if it's empty. This is a valid response since we will look for the array after looking for a database query. If we aren't given any data from outside, then this Activity cannot be executed. It's useless to ask a user to select an item from a blank list.

Remember that it's intended that this Activity be started by another Activity, not directly by the user through the applications menu. For that reason, we want to give useful feedback to ourselves or other developers when the Activity is not used in the way it's intended.

Time for action – creating the CursorAdapter

The CursorAdapter is much more complex to set up than the ArrayAdapter. For one thing, we offer more options with the CursorAdapter than we did with the ArrayAdapter. Our CursorAdapter can be made to display either one or two line list items, based on whether there are one or two columns specified. While the ArrayAdapter includes some default filtering logic, we need to provide a little more support for the CursorAdapter.

  1. To start with, we allow for two different column naming conventions to be used, along with some defaults. Declare a utility method to f?ind the expected column names from the Intent:

    private String getColumnName(
    final Intent intent,
    String primary,
    String secondary,
    String def) {

  2. First, try and use the primary attribute name to get a column name:

    String col = intent.getStringExtra(primary);

  3. If the column name is null, try the secondary attribute name:

    if(col == null) {
    col = intent.getStringExtra(secondary);
    }

  4. If the column name is still null, use the default value:

    if(col == null) {
    col = def;
    }

  5. return the column name:

    return col;

  6. Now, declare another utility method that will create the actual CursorAdapter to be used in the ListView:

    private ListAdapter createCursorAdapter(Intent intent) {

  7. Find the name of the first column to be displayed:

    final String line1 = getColumnName(intent, "name", "line1",
    "name");

  8. Find the name of the optional second column to be displayed:

    String line2 = getColumnName(
    intent, "description", "line2", null);

  9. We now have two possible paths—a single line list item, or a double line list item. These are very similar in their construction, so we declare some variables to hold those values that are different between the two paths:

    int listItemResource;
    final String[] columns;
    String[] displayColumns;
    int[] textIds;

  10. If the line2 column name has been specified, we use the following code:

    if(line2 != null) {

  11. We will be using a two-line list item resource:

    listItemResource = android.R.layout.two_line_list_item

  12. The database query needs to select the _id column, and both columns that were specified in the Intent:

    columns = new String[]{"_id", line1, line2};

  13. However, the list items will only display the two specified columns:

    displayColumns = new String[]{line1, line2};

  14. The CursorAdapter needs to know the resource IDs of the TextView widgets declared in the two_line_list_item resource:

    textIds = new int[]{android.R.id.text1, android.R.id.text2};

  15. If the second column name was not specified in the Intent, the ListView should have single-line items:

    else {
    listItemResource = android.R.layout.simple_list_item_1;

  16. We only need to request the _id column, and the single column name:

    columns = new String[]{"_id", line1};

  17. The items in the list should have the contents of the requested column in them:

    displayColumns = new String[]{line1};

  18. We don't need to tell the CursorAdapter which widget ID to look for in a single-line list item resource:

    textIds = null;

  19. After the else clause, we will have the required variables populated. We can run our initial database query and get the full list of data for presenting it to the user:

    Cursor cursor = managedQuery(
    intent.getData(),
    columns,
    null,
    null,
    line1);

  20. We can now create the CursorAdapter to wrap the database Cursor object for the ListView. We use the SimpleCursorAdapter implementation:

    CursorAdapter cursorAdapter = new SimpleCursorAdapter(
    this,
    listItemResource,
    cursor,
    displayColumns,
    textIds);

  21. In order for the user to filter the list, we need to give the CursorAdapter a FilterQueryProvider. Declare the FilterQueryProvider as an anonymous inner class:

    cursorAdapter.setFilterQueryProvider(
    new FilterQueryProvider() {

  22. Inside the anonymous FilterQueryProvider, declare the runQuery method which will be called each time the user types a key:

    public Cursor runQuery(CharSequence constraint) {

  23. We can return a managedQuery which simply performs an SQL LIKE on the first column that we are rendering in the ListView:

    return managedQuery(
    intent.getData(),
    columns,
    line1 + " LIKE ?",
    new String[] {constraint.toString() + '%'},
    line1);

  24. Finally, the createCursorAdapter method can return the CursorAdapter:

    return cursorAdapter;

What just happened?

This utility method handles the creation of the CursorAdapter for the time when a query Uri is specified in our Intent. This structure allows filtering of very large data sets, since it's (generally) built on top of the SQL Lite database. Its performance is directly related to the structure of the database table it will query.

As a result of the potentially enormous size of a database query, the CursorAdapter classes don't do any filtering of the data set themselves. Instead, you are required to implement the FilterQueryProvider interface to create and run a new query for each change to the filter. In the preceding example, we created a Cursor which is exactly the same as the default Cursor, but we add selection and selectionArgs to the query. This LIKE clause will tell SQL Lite to only return rows starting with the filter that the user has typed.

Android User Interface Development: Beginner's Guide Quickly design and develop compelling user interfaces for your Android applications with this book and eBook
Published: February 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

Time for action – setting up the ListView

We now have implementations to create both types of ListAdapter that this Activity can filter. Now we need a utility method to figure out which one to use, and return it; and then we want to use the new utility method to set the ListAdapter on the ListView widget.

  1. Declare a new method to create the desired ListAdapter object:

    protected ListAdapter createListAdapter() {

  2. Fetch the Intent object that was used to start the Activity:

    Intent intent = getIntent();

  3. If the data Uri in the Intent is not null, return a CursorAdapter for the given Intent. Otherwise, return an ArrayAdapter for the given Intent:

    if(intent.getData() != null) {
    return createCursorAdapter(intent);
    else {
    return createArrayAdapter(intent);
    }

  4. In the onCreate method, after finding the two View objects from the layout, create the desired ListAdapter with the new utility method:

    adapter = createListAdapter();

  5. Assign the Filter field to the Filter given by the ListAdapter:

    filter = ((Filterable)adapter).getFilter();

  6. Set the ListAdapter on the ListView:

    list.setAdapter(adapter);

What just happened?

This code now references both the created ListAdapter object and the Filter that it works with. You'll notice that if you run the application now, you'll get a Force Close dialog when you open it. That's because the code now requires some sort of data to populate the ListView with. While not desirable for a normal application, this is really a reusable component which could be used in a variety of situations.

Time for action – filtering the list

Although the code is all set up to display the list, and even to filter it, we haven't yet attached the EditText box to the ListView, so typing in the EditText will have absolutely no effect at the moment. We need to listen for changes to the EditText box, and request that the ListView be filtered based on what is typed. This will involve the ListItemSelectionActivity class listening for events on the EditText and then asking the Filter object to narrow the available set of items.

  1. The ListItemSelectionActivity should be made to implement the TextWatcher interface:

    public class ListItemSelectionActivity extends Activity
    implements TextWatcher

  2. After setting the ListAdapter on the ListView in the onCreate method, add the ListItemSelectionActivity as a TextWatcher on the EditText widget:

    input.addTextChangedListener(this);

  3. You'll need to declare empty implementations of the beforeTextChanged and onTextChanged methods, since we're not really interested in these events:

    public void beforeTextChanged(
    CharSequence s,
    int start,
    int count,
    int after) {
    }
    public void onTextChanged(
    CharSequence s,
    int start,
    int count,
    int after) {
    }

  4. Then declare the afterTextChanged method, which we are interested in:

    public void afterTextChanged(Editable s) {

  5. In the afterTextChanged method, we simply ask the Filter of the current ListAdapter to filter the ListView:

    filter.filter(s);

What just happened?

The TextWatcher interface is used in order to track changes to a TextView widget. Implementations will be able to listen for any changes to the actual content of the TextView, regardless of the source of the change. While the OnKeyListener and KeyboardListener interfaces are mostly there to handle hardware keyboard events, the TextWatcher handles changes from hardware keyboards, soft keyboards, and even internal calls to TextView.setText.

Time for action – returning the selection

The ListItemSelectionActivity can now be used to display a list of possible items, and filter through them by typing in an EditText above the ListView. However, we have no way of letting the user actually select one of the options from the ListView in order to pass it back to our parent Activity. This requires nothing more than a simple implementation of the OnItemClickListener interface.

  1. The ListItemSelectionActivity class now needs to implement the OnItemClickListener interface:

    public class ListItemSelectionActivity extends Activity
    implements TextWatcher, OnItemClickListener {

  2. After registering as a TextWatcher in the onCreate method, register as an OnItemClickListener on the ListView:

    OnItemClickListener on the ListView:
    list.setOnItemClickListener(this);

  3. Override the onItemClick method to listen for the user's selection:

    public void onItemClick(
    AdapterView<?> parent,
    View clicked,
    int position,
    long id) {

  4. Create an empty Intent object to pass back to our parent Activity:

    Intent data = new Intent();

  5. If the ListAdapter is a CursorAdapter, the id passed into the onItemClick will be the database _id column value for the selection. Add this value to the Intent:

    if(adapter instanceof CursorAdapter) {
    data.putExtra("selection", id);

  6. If the ListAdapter is not a CursorAdapter, we add the actual selected Object to the Intent:

    else {
    data.putExtra(
    "selection",
    (Serializable)parent.getItemAtPosition(position));
    }

  7. Set the result code to RESULT_OK, and pass the Intent back:

    setResult(RESULT_OK, data);

  8. The user has made their selection, so we're now finished with this part:

    finish();

What just happened?

The ListItemSelectionActivity is now complete and ready for use. It offers much the same functionality as an AutoCompleteTextView, except that being an independent Activity, it offers the user a much larger list of suggestions, and the user must select an item from the ListView instead of being able to simply type their input data.

Using the ListItemSelectionActivity

You will need to specify what data you want the user to select from, as part of the Intent that starts a ListItemSelectionActivity. As already discussed, there are effectively two paths:

  • Pass in an array of some sort (which is perfect for use within your own application)
  • Give it a database query Uri and the column names you want displayed (which is great if you want to use it from another application)

Since the ListItemSelectionActivity returns its selection (and it's not much use if it doesn't), you need to start it with the startActivityForResult method instead of the normal startActivity method. If you want to pass it an array of String objects to select from, you could use something similar to the following intent = new Intent(this, ListItemSelectionActivity.class):

intent.putExtra("data", new String[] {
"Blue",
"Green",
"Red",
// more colors
});
startActivityForResult(intent, 101);

Given enough colors in the above data array, you would be presented with a ListItemSelectionActivity screen which could be filtered for the user's desired color. The following is a screenshot of how the resulting screen would look:

Android User Interface Development: Validating and Handling Input Data

In order to receive the results back from the ListItemSelectionActivity, you will need to listen for the results in the onActivityResult method. If, for example, you simply wanted to Toast the result of the confirmed selection, you could use the following code:

@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 101 && resultCode == RESULT_OK) {
Object obj = data.getSerializableExtra("selection");
Toast.makeText(
this,
String.valueOf(obj),
Toast.LENGTH_LONG).show();
}
}

Finally, how would you use a database query with the ListItemSelectionActivity? This is amazingly easy to show, and is probably the most exciting feature of the ListItemSelectionActivity. The following code snippet will let the user select one of the contacts from their phone book:

Intent intent = new Intent(
this,
ListItemSelectionActivity.class);
intent.setData(People.CONTENT_URI);
intent.putExtra("line1", People.NAME);
intent.putExtra("line2", People.NUMBER);
startActivityForResult(intent, 202);

Have a go hero!

The ListItemSelectionActivity can filter and select almost anything. Try building up a list of all the countries in the world (many such lists are available online), and then create an Activity which asks you to select one using a ListItemSelectionActivity.

Summary

How you accept input from your users, and how you validate that input plays a crucial part in the overall experience your users will have with your application. Software should help the users along and tell them what it expects at each step. This not only makes an application easier to use, but also much faster to work with.

Using the ListItemSelectionActivity, will often help your users trawl through large data sets, while protecting them from making a choice that they don't want to, or is invalid. It's a very commonly used type of widget and is seen in many different applications (in various forms). Android, at present, doesn't have a generic class to perform this job quite as easily.


Further resources related to this subject:


About the Author :


Jason Morris

Jason Morris has worked on software as diverse as fruit tracking systems, insurance systems, and travel search and booking systems and has been writing software for as long as he can remember. Currently working as a Software Architect for an exciting travel company in South Africa he works on multiple front-end systems utilizing a variety of Java-based technologies.

Books From Packt


Flash Development for Android Cookbook
Flash Development for Android Cookbook

Android Application Testing Guide
Android Application Testing Guide

Android 3.0 Application Development Cookbook
Android 3.0 Application Development Cookbook

iPhone JavaScript Cookbook
iPhone JavaScript Cookbook

Xcode 4 iPhone Development Beginner's Guide
Xcode 4 iPhone Development Beginner's Guide

Core Data iOS Essentials
Core Data iOS Essentials

Cocos2d for iPhone 0.99 Beginner's Guide
Cocos2d for iPhone 0.99 Beginner's Guide

MeeGo 1.0 Mobile Application Development Cookbook
MeeGo 1.0 Mobile Application Development Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software