Monthly Archives: June 2012

How to add autocompletion to an EditText

Finally after months and months of saying that I would add development articles to my blog, here is the first one :)

EditText is a component that everybody knows and it is the primary way to get user input in an Android application.
What is really less known is its cousin : AutoCompleteTextView. It’s a subclass of EditText that allows you to display possible autocompletions to the user depending on what he/she already entered in the EditText.

This article will focus on how to use this component and how to bind different data sources to provide the completions.

1 – Presentation of AutoCompleteTextView and simple autocompletion

AutoCompleteTextView is used exactly the same way as an EditText in your layouts (as it’s a subclass of EditText). You just need to replace your EditText by an AutoCompleteTextView and this part is done !

Now that our layout is ready, let’s look at the code.

For this first example we are going to use a really simple example (based on the documentation of AutoCompleteTextView class in the Android SDK). The possible autocompletions are going to be a fixed list of countries.

1
2
3
4
5
6
7
8
private static final String[] COUNTRIES = new String[] {
	"Belgium", "France", "Italy", "Germany", "Spain"
};
 
// In the onCreate method
AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.actv_country);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, COUNTRIES);
textView.setAdapter(adapter);

We just have an array of Strings as the data source and we just bind via an ArrayAdapter the list of countries> The component will manage them by itself.

Now let’s look at a little more complex example.

2 – Email autocompletion

Let’s say we have a login screen in our application where the login is an email. An email is quite long and really annoying to type in, especially with a virtual keyboard.

We don’t know all the email addresses the user could potentially have. What we know however from his phone is all the email addresses he/she used as an account (Google, Facebook, Twitter, …).
We can use them as a data source to provide completion.

1
2
3
4
5
6
7
8
9
10
11
12
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$", Pattern.CASE_INSENSITIVE);
 
// In the onCreate method
AutoCompleteTextView editTextLogin = (AutoCompleteTextView) findViewById(R.id.actv_login);
Account[] accounts = AccountManager.get(this).getAccounts();
Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
	if (EMAIL_PATTERN.matcher(account.name).matches()) {
		emailSet.add(account.name);
	}
}
editTextLogin.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, new ArrayList<String>(emailSet)));

This time instead of using a fixed array of Strings, we load the user accounts through the AccountManager.
As some accounts may not have an email address linked to them, we filter them and keep only the ones who matches an email regex. We also use a Set to remove the duplicates.
Then we use the same way as before to bind them to the AutoCompleteTextView through an ArrayAdapter.

One thing to know if you want to use this snippet of code as is in your project : it requires the permission android.permission.GET_ACCOUNTS to be able to read the user accounts.

You can use this example with any other data source as long as you manage to get an array of Strings to display.

And here is how it looks :

3 – Webservice autocompletion

For the first 2 examples, we only used static data. Now let’s use dynamic data for the auto-completion. The data is going to be returned from a web service which is going to be called every time the user input changes in the field.
For this example, we are going to write the code to add autocompletion to an address field :

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
AutoCompleteTextView editTextAddress = (AutoCompleteTextView)findViewById(R.id.actv_address);
editTextAddress.setAdapter(new AutoCompleteAdapter(this));
 
// And the corresponding Adapter
private class AutoCompleteAdapter extends ArrayAdapter<Address> implements Filterable {
 
	private LayoutInflater mInflater;
	private Geocoder mGeocoder;
	private StringBuilder mSb = new StringBuilder();
 
	public AutoCompleteAdapter(final Context context) {
		super(context, -1);
		mInflater = LayoutInflater.from(context);
		mGeocoder = new Geocoder(context);
	}
 
	@Override
	public View getView(final int position, final View convertView, final ViewGroup parent) {
		final TextView tv;
		if (convertView != null) {
			tv = (TextView) convertView;
		} else {
			tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
		}
 
		tv.setText(createFormattedAddressFromAddress(getItem(position)));
		return tv;
	}
 
	private String createFormattedAddressFromAddress(final Address address) {
		mSb.setLength(0);
		final int addressLineSize = address.getMaxAddressLineIndex();
		for (int i = 0; i < addressLineSize; i++) {
			mSb.append(address.getAddressLine(i));
			if (i != addressLineSize - 1) {
				mSb.append(", ");
			}
		}
		return mSb.toString();
	}
 
	@Override
	public Filter getFilter() {
		Filter myFilter = new Filter() {
			@Override
			protected FilterResults performFiltering(final CharSequence constraint) {
				List<Address> addressList = null;
				if (constraint != null) {
					try {
						addressList = mGeocoder.getFromLocationName((String) constraint, 5);
					} catch (IOException e) {
					}
				}
				if (addressList == null) {
					addressList = new ArrayList<Address>();
				}
 
				final FilterResults filterResults = new FilterResults();
				filterResults.values = addressList;
				filterResults.count = addressList.size();
 
				return filterResults;
			}
 
			@SuppressWarnings("unchecked")
			@Override
			protected void publishResults(final CharSequence contraint, final FilterResults results) {
				clear();
				for (Address address : (List<Address>) results.values) {
					add(address);
				}
				if (results.count > 0) {
					notifyDataSetChanged();
				} else {
					notifyDataSetInvalidated();
				}
			}
 
			@Override
			public CharSequence convertResultToString(final Object resultValue) {
				return resultValue == null ? "" : ((Address) resultValue).getAddressLine(0);
			}
		};
		return myFilter;
	}
}

As the data is dynamic depending on the current value, we can’t just provide beforehand the list of elements. Instead we create an ArrayAdapter with a special Filter.
Everytime the input changes, the performFiltering method is called and we retrieve in it the corresponding values from the webservice. These values are then transfered through the FilterResults object to the publishResults method where we replace the current elements with the new ones.

In our specific case, our webservice is the SDK Geocoder which calls the Maps server to get possible addresses based on input provided by the user.
As the result type, Address, is a complex object and we only want to display its formatted address while still keeping the object in the Adapter for further use, we also have to override both getView and convertResultToString methods :

  • The first one, getView, to choose how to display our special object in the list of possible autocompletions.
  • convertResultToString is used internally by the AutoCompleteTextView to choose what to display in the EditText field when the user selects one of the completions (in our case, the formatted address).

If the data returned by your webservice is only a String, you don’t need to do that (except if you want a special format, like upper case or something).

One thing to remember, the code in the performFiltering method is executed in a worker thread so we can directly access the webservice (in our case the Geocoder) to get the corresponding results even if it’s a long process.

4 – Conclusion

Normally now you know how to add autocompletion to your applications :)

If you have some questions about it, feel free to comment this article or to contact me via email !

And I have made a small application containing all these examples. Here is the APK and the project source code.

Page optimized by WP Minify WordPress Plugin