Today I was implementing an autofilling login form for Android Application which had the following requirements:
- When user logs in he can check “Remember credentials” box to make the application remember the previous credentials
- When user would log in next time if he had saved single username before he would be straightly offered with the previously saved credentials
- If the user had previously entered multiple credentials a drop down will be shown that will let the user decide which of the credentials to use
An obvious choice to perform this task is to use AutoCompleteTextView, but unfortunately it has couple of limitations, which I posted about before, when I created InstantAutoCompleteTextView.
InstantAutoCompleteTextView solved some of these problems, but after that I still had couple of them left to overcome, and most important of them, the AutoCompleteTextView wouldn’t offer auto completion if you haven’t inputted any characters, or that after filling field completely, it would still show the user as hint.
I was using ArrayAdapter as AutoCompleteTextViews username adapter, to list matching usernames, but ArrayAdapters filter uses start of each word for matching prefixes, but in my case, as I was matching for usernames, I would need to match whole username, so basically I had to extend the ArrayAdapter to perform this operation.
Unfortunately ArrayAdapter stores the objects that it has as private field, so my extending adapter wouldn’t have access to them, without reimplementing most of its logic, so what I did, was open the ArrayAdapters source and reimplement most of its business, and reimplement the Filter it provides.
How I changed the Filter is that instead of matching for each word, I matched the whole string, and if the inputted text was exact to the adapters object, it wouldn’t be included, as when user had an exact match, it wouldn’t be needed to be shown anymore.
private class ExactArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if(originalObjects == null) { originalObjects = Collections.synchronizedList(objects); } if (prefix == null || prefix.length() == 0) { List<T> list = new ArrayList<>(originalObjects); results.values = list; results.count = list.size(); } else { String prefixString = prefix.toString().toLowerCase(); final List<T> values = Collections.synchronizedList(originalObjects); final int count = values.size(); final ArrayList<T> newValues = new ArrayList<>(); for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = value.toString().toLowerCase(); // Match against the whole with prefix and disinclude exact matches if (valueText.startsWith(prefixString) && !valueText.equals(prefixString)) { newValues.add(value); } } results.values = newValues; results.count = newValues.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { objects = (List<T>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } }
Now I have my Filter that makes the popup disappear after matching input is typed into.
Then I just have to implement the piece of code that handles the password autofill, and how I did that, is I stored set of user credentials as plain text(very secure) in JSON string to preferences, and now I could just when the AutoCompleteTextViews value changes, look up if there’s a already saved password and use that.
So what that basically meant is to add an TextWatcher and on afterTextChanged perform this check and set a fake password on the password field and store the selected password, or “override password” on other field to be used on login, so the potentially blackhat user can’t use the number of characters in the password to gain access to the password more easily.
Also now that I knew if the password was “fake” and “overriden” I could add a TextWatcher and another afterTextChanged callback that would reset the password if override was present, to imitate usual autofill behaviour.
Below you can see the final result
https://youtube.com/watch?v=tng9motar1E
Thanks for reading!