AutocompleteTextView de Android con ArrayAdapter y Filter

Información de Backgroud

La aplicación que estoy escribiendo actualmente trata de lugares. El método principal para crear un lugar es introducir una dirección. Proporcionar una manera conveniente de hacerlo es muy importante para el UX.

Solución actual

Después de investigar el problema un tiempo llegué con la conclusión de que la mejor manera de hacerlo es un área de texto con autocompletado basado en la ubicación actual y la entrada del usuario (como en la aplicación Google Map). Dado que, por desgracia, esta característica no está incluida en el API de Google Map, tengo que implementarla por mí mismo.

Implementación actual

Para obtener sugerencias de direcciones, utilizo Geocoder . Estas sugerencias se proporcionan a un AutoCompleteTextView por un ArrayAdaptor personalizado a través de un filtro personalizado.

El ArrayAdapter (Se ha eliminado el código)

public class AddressAutoCompleteAdapter extends ArrayAdapter<Address> { @Inject private LayoutInflater layoutInflater; private ArrayList<Address> suggested; private ArrayList<Address> lastSuggested; private String lastInput = null; private AddressLoader addrLoader; public AddressAutoCompleteAdapter(Activity activity, AddressLoaderFactory addrLoaderFact, int maxSuggestionsCount) { super(activity,R.layout.autocomplete_address_item); suggested = new ArrayList<Address>(); lastSuggested = new ArrayList<Address>(); addrLoader = addrLoaderFact.create(10); } ... @Override public Filter getFilter() { Filter myFilter = new Filter() { ... @SuppressWarnings("unchecked") @Override protected FilterResults performFiltering(CharSequence constraint) { Log.d("MyPlaces","performFiltring call"); FilterResults filterResults = new FilterResults(); boolean debug = false; if(constraint != null) { // Load address try { suggested = addrLoader.load(constraint.toString()); } catch(IOException e) { e.printStackTrace(); Log.d("MyPlaces","No data conection avilable"); /* ToDo : put something useful here */ } // If the address loader returns some results if (suggested.size() > 0) { filterResults.values = suggested; filterResults.count = suggested.size(); // If there are no result with the given input we check last input and result. // If the new input is more accurate than the previous, display result for the // previous one. } else if (constraint.toString().contains(lastInput)) { debug = true; Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString()); filterResults.values = lastSuggested; filterResults.count = lastSuggested.size(); } else { filterResults.values = new ArrayList<Address>(); filterResults.count = 0; } if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; } lastInput = constraint.toString(); } if (debug) { Log.d("MyPlaces","filter result count :" + filterResults.count); } return filterResults; } @Override protected void publishResults(CharSequence contraint, FilterResults results) { AddressAutoCompleteAdapter.this.notifyDataSetChanged(); } }; return myFilter; } ... 

AddressLoader (se ha eliminado algún código)

 public class GeocoderAddressLoader implements AddressLoader { private Application app; private int maxSuggestionsCount; private LocationManager locationManager; @Inject public GeocoderAddressLoader( Application app, LocationManager locationManager, @Assisted int maxSuggestionsCount){ this.maxSuggestionsCount = maxSuggestionsCount; this.app = app; this.locationManager = locationManager; } /** * Take a string representing an address or a place and return a list of corresponding * addresses * @param addrString A String representing an address or place * @return List of Address corresponding to the given address description * @throws IOException If Geocoder API cannot be called */ public ArrayList<Address> load(String addrString) throws IOException { //Try to filter with the current location Location current = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); Geocoder geocoder = new Geocoder(app,Locale.getDefault()); ArrayList<Address> local = new ArrayList<Address>(); if (current != null) { local = (ArrayList<Address>) geocoder.getFromLocationName( addrString, maxSuggestionsCount, current.getLatitude()-0.1, current.getLongitude()-0.1, current.getLatitude()+0.1, current.getLongitude()+0.1); } if (local.size() < maxSuggestionsCount) { ArrayList<Address> global = (ArrayList<Address>) geocoder.getFromLocationName( addrString, maxSuggestionsCount - local.size()); for (Address globalAddr : global) { if (!containsAddress(local,globalAddr)) local.add(globalAddr); } } return local; } ... 

Problemas

Esta implementación funciona, pero no es perfecta.

El AddressLoader tiene un comportamiento extraño con algunas entradas.
Por ejemplo, si el usuario desea introducir esta dirección

 10 rue Guy Ropartz 54600 Villers-les-Nancy 

Cuando haya ingresado:

 10 rue Guy 

Hay algunas sugerencias pero no la dirección deseada.
Si continúa introduciendo texto cuando se alcanza esta entrada:

 10 rue Guy Ro 

Las sugerencias desaparecen. La dirección deseada aparece finalmente cuando la entrada es

 10 rue Guy Ropart 

Creo que esto es inquietante para el usuario. Es extraño que no hay ninguna sugerencia para "10 rue Guy Ro", mientras que hay sugerencias para "10 rue Guy" y "10 rue Guy Ropart"

Una posible solución

Me imagino una solución posible para este problema y tratar de implementarlo. La idea es mantener la sugerencia anterior si después de escribir una dirección más larga no hay dirección sugerida por AdressLoader. En nuestro ejemplo mantenga "10 rue Guy" sugerencias cuando la entrada es "10 rue Guy Ro" .

Desafortunadamente mi código no funciona. Cuando estoy en esta situación se alcanza la buena parte del código:

 else if (constraint.toString().contains(lastInput)) { debug = true; Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString()); filterResults.values = lastSuggested; filterResults.count = lastSuggested.size(); } 

Y FilterResult devuelto por performFiltering está lleno de las buenas sugerencias pero no aparece en la vista. Tal vez me pierda algo en publishResults

Preguntas

  • Geocoder tiene alguna vez un comportamiento extraño, tal vez hay un mejor enfoque para obtener sugerencias de direcciones?
  • ¿Me puede sugerir una solución mejor que la que describí?
  • ¿Cuál es el problema en la implementación de mi solución?

Actualizar
Por cuestiones de compatibilidad, implementé un nuevo AddressLoader basado en Google Geocode Http API (porque el servicio predeterminado de geocodificador de Android no está disponible en todos los dispositivos). Reproduce exactamente el mismo comportamiento extraño.

El Geocoder api no está diseñado para dar sugerencias sobre cadenas parciales como "10 rue Guy Ro". Usted puede probar esto en Google Maps, entrar en "10 rue Guy Ro" y dejar un espacio. No obtendrás ninguna sugerencia (Google Maps utiliza Geocoder), pero al eliminar el espacio obtendrás sugerencias sobre la cadena parcial (Google Maps utiliza la API privada). Puede utilizar el api de Geocoder de la misma manera que utiliza Google Maps, obtenga la sugerencia sólo después de que el espacio de entrada del usuario en la cadena que está escribiendo.

Intente cambiar esta parte del código:

 if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; } lastInput = constraint.toString(); 

dentro de esto:

 if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; lastInput = constraint.toString(); } 

Creo que su problema es que está configurando lastSuggested a sugerido (a través de filterResults.values) y luego, en el paso siguiente, se está actualizando sugerido -> lastSuggested se establecerá al mismo valor sugerido como Java pasa variables por referencia a menos primitivas .

Entonces, en lugar de

If (filterResults.count> 0) {lastSuggested = (ArrayList) filterResults.values; }

Deberías intentarlo

 if ( filterResults.count > 0) { lastSuggested.clear(); ArrayList<Address> tempList = (ArrayList<Address>) filterResults.values; int i = 0; while (i < tempList.size()){ lastSuggested.add(tempList.get(i)); i += 1; } } 
  • AutoCompleteTextView respaldado por CursorLoader
  • Diacríticos / caracteres internacionales en AutoCompleteTextView
  • El teclado oculta el menú desplegable AutoCompleteTextView
  • Mostrando resultados de autocompletetextview como sugerencias arriba del teclado
  • La lista de sugerencias de AutocompleteTextView aumenta
  • Eliminar subrayado de AutoCompleteTextView a través de xml
  • Cómo filtrar los resultados de AutoCompleteTextView?
  • AutocompleteTextView dinámico con ArrayAdapter y TextWatcher
  • ¿Cómo comprobar el estado dropDown (mostrado u ocultado) en autoCompleteTextView?
  • Android: restringir al usuario para seleccionar sólo texto de autocompletado
  • AutoCompleteTextView no muestra el menú desplegable cuando presiono espacio después de escribir una palabra completa
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.