This answer builds on others, but most of all on Carl Anderson's. It is based on AutoCompleteTextView option, and I am going to look one by one at each element of the solution.
First of all, when creating the view, let's grab the ACTV and associate it with a custom adapter:
public View onCreateView([...]) {
.
.
TaxonomyDatabase db = new TaxonomyDatabase(getContext());
List<Epithet> allEpithets = db.getAllGenera();
EpithetAdapter hints = new EpithetAdapter(getContext(),
android.R.layout.select_dialog_item, allEpithets);
AutoCompleteTextView widget = rootView.findViewById(R.id.etCollectSpecies);
widget.setThreshold(2); // start hinting from second character
widget.setAdapter(hints);
The TaxonomyDatabase.getAllGenera
grabs all genera from the database, with their accepted equivalent, and the family they belong to.
List<Epithet> getAllGenera() {
ArrayList<Epithet> r = new ArrayList<>();
SQLiteDatabase db = getReadableDatabase();
Cursor cr = db.rawQuery(
"select o.epithet, a.epithet, o.phonetic, a.family_name " +
"from taxon o "+"" +
"left join taxon a on o.accepted_id = a.id " +
"where o.rank = 5 " +
"order by o.epithet",
new String[]{});
.
.
Now the EpithetAdapter constructor accepts this list, and first adds it to its internal candidates list, but then again copies each Epithet again in the list: the first instance is for literal match, the second instance is to allow for phonetic match. The logic of this is hidden in Epithet copy constructor, that sets the isExact
field to false
.
EpithetAdapter(Context context, int textViewResourceId, List<Epithet> epithets) {
super(context, textViewResourceId, epithets);
// copy all the epithets twice
mEpithets = new ArrayList<>(epithets.size() * 2);
// first to be used as exact matches (String×4 constructor)
mEpithets.addAll(epithets);
// then again to be used as phonetic matches (copy constructor)
for(Epithet e : epithets) {
mEpithets.add(new Epithet(e));
}
}
The Epithet class has a toString method, that takes care of how the Epithet shows in the ACTV drop down list:
public String toString() {
if (accepted != null) {
return epithet + " → " + accepted;
} else {
return epithet + " (" + family + ")";
}
}
the Filter in the EpithetAdapter does the rest of the task: when user clicks on a suggestion, ConvertResultToString
is invoked:
public String convertResultToString(Object resultValue) {
return ((Epithet)resultValue).epithet;
}
Filtering itself, that is the performFiltering
method, is just the logical consequence of all said up to now:
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null) {
String exact = constraint.toString();
String phonetic = TaxonomyDatabase.phonetic(exact);
exact = exact.substring(0, 1).toUpperCase() + exact.substring(1).toLowerCase();
ArrayList<Epithet> suggestions = new ArrayList<>();
for (Epithet epithet : mEpithets) {
if (epithet.isExact) {
if (epithet.epithet.startsWith(exact)) {
suggestions.add(epithet);
}
} else {
if (epithet.phonetic.startsWith(phonetic) &&
!epithet.epithet.startsWith(exact)) {
suggestions.add(epithet);
}
}
}
results.values = suggestions;
results.count = suggestions.size();
}
return results;
}
- last minor detail, disable text suggestions (
android:inputType="textNoSuggestions"
in the layout) in order not to dirty user dictionary.
when user types coccus, the software suggests:

when user types »galanth«, literal matches come first:

when user types »calanth«, again literal matches come first:

What you see in the drop down list is Epithet.toString
. Clicking on a hint only copies Epithet.epithet
.