Código Android: TextView con sugerencias de direcciones postales

by Francho Joven

15 Dec 2010

. Comments

Para mejorar la experiencia de usuario es una buena idea añadir ayudas como las sugerencias de autocompletado. En este post vamos a ver un código de ejemplo que va mostrando sugerencias con direcciones postales reales conforme escribimos en un TextView.

Para ello usaremos la clase Geocoder incluida en el SDK que iremos invocando conforme el usuario teclee su texto.

Existen varios enfoques para poder hacer esto: @jbeerdev me ha sugerido usar un SuggestionProvider asociado a un SearchManager tal y como describen en el blog de Frogtek, es sin duda una idea interesante que tendré que investigar.

La solución que he implementado se basa en crear un Adapter que se encarga de obtener la lista de sugerencias y asociarlo a una vista de tipo AutoCompleteTextView. Cuando el texto va cambiando realizaremos llamadas al adapter para que recargue la lista de sugeridos en un segundo plano (gracias a un AsyncTask).

El funcionamiento de este enfoque me ha sorprendido gratamente ya que el tiempo de respuesta es menor del que me esperaba. También creo que la solución basada en SearchManager es bastante interesante y merece ser estudiada.

¿Conocéis algún otro enfoque posible? vuestros comentarios serán bienvenidos ;-)

<br />
/**<br />
 *  GeoAutoComplete - an AutoCompleteTextView with real postal address suggestions<br />
 *<br />
 *  Copyright (C) 2010 Francho Joven<br />
 *<br />
 *  This program is free software: you can redistribute it and/or modify<br />
 *  it under the terms of the GNU General Public License as published by<br />
 *  the Free Software Foundation, either version 3 of the License, or<br />
 *  (at your option) any later version.<br />
 *<br />
 *  This program is distributed in the hope that it will be useful,<br />
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the<br />
 *  GNU General Public License for more details.<br />
 *<br />
 *  You should have received a copy of the GNU General Public License<br />
 *  along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.<br />
 */<br />
package org.francho.pruebas.geoautocomplete;</p>
<p>import java.util.ArrayList;<br />
import java.util.List;</p>
<p>import android.app.Activity;<br />
import android.content.Context;<br />
import android.location.Address;<br />
import android.location.Geocoder;<br />
import android.os.AsyncTask;<br />
import android.os.Bundle;<br />
import android.text.Editable;<br />
import android.text.TextWatcher;<br />
import android.util.Log;<br />
import android.view.KeyEvent;<br />
import android.view.LayoutInflater;<br />
import android.view.View;<br />
import android.view.View.OnKeyListener;<br />
import android.widget.ArrayAdapter;<br />
import android.widget.AutoCompleteTextView;<br />
import android.widget.TextView;<br />
import android.widget.TextView.OnEditorActionListener;</p>
<p>public class MainActivity extends Activity implements TextWatcher {</p>
<p>	private PostalAddressAdapter adapter;<br />
	private AutoCompleteTextView addressView;</p>
<p>	/** Called when the activity is first created. */<br />
	@Override<br />
	public void onCreate(Bundle savedInstanceState) {<br />
		super.onCreate(savedInstanceState);<br />
		setContentView(R.layout.main);</p>
<p>		// The autocomplete text view<br />
		addressView = (AutoCompleteTextView) findViewById(R.id.address);<br />
		addressView.addTextChangedListener(this);</p>
<p>		// add our suggestions adapter<br />
		adapter = new PostalAddressAdapter(MainActivity.this);<br />
		addressView.setAdapter(adapter);<br />
	}</p>
<p>	/**<br />
	 * When the text changed we reload the addresslist<br />
	 */<br />
	public void onTextChanged(CharSequence s, int start, int before, int count) {<br />
		adapter.setAddressList(addressView.getText().toString());<br />
	}</p>
<p>	/**<br />
     *<br />
     */<br />
	public void beforeTextChanged(CharSequence s, int start, int count,<br />
			int after) {<br />
		// Do nothing<br />
	}</p>
<p>	/**<br />
	 *<br />
	 */<br />
	public void afterTextChanged(Editable s) {<br />
		// Do nothing<br />
	}</p>
<p>	/**<br />
	 * Adapter that autoload an Address List from a search word<br />
	 *<br />
	 * @author francho<br />
	 *<br />
	 */<br />
	class PostalAddressAdapter extends ArrayAdapter&lt;String&gt; {<br />
		private static final String TAG = &quot;PostalAddressAdapter&quot;;<br />
		private Context context;<br />
		private Geocoder geocoder;<br />
		private List&lt;Address&gt; addressList;<br />
		private LayoutInflater inflater;<br />
		private LoadAddressAsyncTask currentTask;</p>
<p>		/**<br />
		 * constructor<br />
		 *<br />
		 * @param context<br />
		 */<br />
		PostalAddressAdapter(Context context) {<br />
			super(context, android.R.layout.simple_dropdown_item_1line);<br />
			this.context = context;<br />
			this.geocoder = new Geocoder(context);<br />
			this.addressList = new ArrayList&lt;Address&gt;();<br />
		}</p>
<p>		/**<br />
		 * reload the suggestions list based on a searchWords<br />
		 *<br />
		 * @param searchWord<br />
		 */<br />
		public void setAddressList(String searchWord) {<br />
			Log.d(TAG, &quot;new search: &quot; + searchWord);<br />
			if (currentTask != null) {<br />
				currentTask.cancel(true);<br />
				currentTask = null;<br />
			}</p>
<p>			currentTask = new LoadAddressAsyncTask();<br />
			currentTask.execute(searchWord);<br />
		}</p>
<p>		/**<br />
		 * getCount<br />
		 */<br />
		@Override<br />
		public int getCount() {<br />
			return addressList.size();<br />
		}</p>
<p>		/**<br />
		 * getItem<br />
		 */<br />
		@Override<br />
		public String getItem(int position) {<br />
			try {<br />
				Address addr = addressList.get(position);<br />
				return &quot;&quot; + addr.getAddressLine(0) + &quot; - &quot;<br />
						+ addr.getSubAdminArea() + &quot;(&quot; + addr.getCountryName()<br />
						+ &quot;)&quot;;<br />
			} catch (Exception e) {<br />
				return null;<br />
			}<br />
		}</p>
<p>		/**<br />
		 * getItemId<br />
		 */<br />
		@Override<br />
		public long getItemId(int position) {<br />
			return position;<br />
		}</p>
<p>		/**<br />
		 * Async task to load the geo data in background<br />
		 *<br />
		 * @author francho<br />
		 *<br />
		 */<br />
		class LoadAddressAsyncTask extends<br />
				AsyncTask&lt;String, Void, List&lt;Address&gt;&gt; {</p>
<p>			private static final int MAX_RESULTS = 5;</p>
<p>			/**<br />
			 * do the heavy task<br />
			 */<br />
			@Override<br />
			protected List&lt;Address&gt; doInBackground(String... arg0) {<br />
				List&lt;Address&gt; result = new ArrayList&lt;Address&gt;();<br />
				try {<br />
					Log.d(TAG, &quot;Searching by &quot; + arg0[0]);<br />
					result = geocoder.getFromLocationName(&quot;&quot; + arg0[0],<br />
							MAX_RESULTS);</p>
<p>					Log.d(TAG, &quot;Searching by &quot; + arg0[0] + &quot;done. Result:&quot;<br />
							+ result);</p>
<p>					return result;<br />
				} catch (Exception e) {<br />
					e.printStackTrace();<br />
				}</p>
<p>				return result;<br />
			}</p>
<p>			/**<br />
			 * when the load is finished...<br />
			 */<br />
			@Override<br />
			protected void onPostExecute(List&lt;Address&gt; result) {<br />
				addressList = (ArrayList&lt;Address&gt;) result;</p>
<p>				Log.d(TAG, &quot;newAddressList: &quot; + addressList);</p>
<p>				notifyDataSetChanged();<br />
			}</p>
<p>		}<br />
	}</p>
<p>}<br />