Android – Implementing a Google Maps Search Box with AutoCompleteTextView and Geocoder API

This guide will show you how implement a similar search box to the Google Maps app, using the built in AutoCompleteTextView, the Geocoder class, a custom Adapter and a few layout files.

The final result looks like:

AutocompleteTextView with Geocoder class on Android

 

Step 1 – Implement a custom Adapter to add results to the AutocompleteTextView

public class GeoAutoCompleteAdapter extends BaseAdapter implements Filterable {
 
	private static final int MAX_RESULTS = 10;
	private Context mContext;
	private List resultList = new ArrayList();
 
	public GeoAutoCompleteAdapter(Context context) {
		mContext = context;
	}
 
	@Override
	public int getCount() {
		return resultList.size();
	}
 
	@Override
	public GeoSearchResult getItem(int index) {
		return resultList.get(index);
	}
 
	@Override
	public long getItemId(int position) {
		return position;
	}
 
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = inflater.inflate(R.layout.geo_search_result_item, parent, false);
		}
 
		((TextView) convertView.findViewById(R.id.geo_search_result_text)).setText(getItem(position).getAddress());
 
		return convertView;
	}
 
	@Override
	public Filter getFilter() {
		Filter filter = new Filter() {
			@Override
			protected FilterResults performFiltering(CharSequence constraint) {
				FilterResults filterResults = new FilterResults();
				if (constraint != null) {
					List locations = findLocations(mContext, constraint.toString());
 
					// Assign the data to the FilterResults
					filterResults.values = locations;
					filterResults.count = locations.size();
				}
				return filterResults;
			}
 
			@Override
			protected void publishResults(CharSequence constraint, FilterResults results) {
				if (results != null && results.count > 0) {
					resultList = (List) results.values;
					notifyDataSetChanged();
				} else {
					notifyDataSetInvalidated();
				}
			}
		};
		return filter;
	}
 
        private List<GeoSearchResult> findLocations(Context context, String query_text) {
 
		List<GeoSearchResult> geo_search_results = new ArrayList<GeoSearchResult>();
 
		Geocoder geocoder = new Geocoder(context, context.getResources().getConfiguration().locale);
                List<Address> addresses = null;
 
                try {
                     // Getting a maximum of 15 Address that matches the input text
                     addresses = geocoder.getFromLocationName(query_text, 15);
 
                     for(int i=0;i<addresses.size();i++){
                          Address address = (Address) addresses.get(i);
                          if(address.getMaxAddressLineIndex() != -1)
                          {
                         	geo_search_results.add(new GeoSearchResult(address));
                          }
                     }
 
 
                } catch (IOException e) {
                    e.printStackTrace();
                }
 
                return geo_search_results;
	}
}

This adapter class will handle the Geocoder search request as well as filtering and drawing the results in the dropdown menu. You’ll need to go ahead and create a new class in your project named GeoAutoCompleteAdapter.

The magic happens in the getFilter function which fires off the Geocoder request and returns the results.

Step 2 – Create a custom class to handle the result

I’ve used a custom class here to handle the Geocoder address result so that if you wanted to create some logic to perhaps format the string before it’s returned to the array adapter you can do so.

public class GeoSearchResult {
 
	private Address address;
 
	public GeoSearchResult(Address address)
	{
		this.address = address;
	}
 
	public String getAddress(){
 
		String display_address = "";
 
		display_address += address.getAddressLine(0) + "\n";
 
		for(int i = 1; i < address.getMaxAddressLineIndex(); i++)
		{
			display_address += address.getAddressLine(i) + ", ";
		}
 
		display_address = display_address.substring(0, display_address.length() - 2);
 
		return display_address;
	}
 
	public String toString(){
		String display_address = "";
 
		if(address.getFeatureName() != null)
		{
			display_address += address + ", ";
		}
 
		for(int i = 0; i < address.getMaxAddressLineIndex(); i++)
		{
			display_address += address.getAddressLine(i);
		}
 
		return display_address;
	}
}

Step 3 – Create the list item view for the results from the AutocompleteTextView

This view will be used for each result returned from the Geocoder request. Feel free to redesign this as you see fit.

Save this under res/layout/geo_search_result.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/geo_search_result_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp" />

Step 4 – Create a custom AutoCompleteTextView so you don’t spam the Geocoder

With a normal AutoCompleteTextView the filter function fires on every keypress. If you did this with an address on the Geocoder API the requests will be throttled and no results will be returned. So implementing a custom AutoCompleteTextView you can Override the performFiltering where we can use a Handler thread which fires after a delay period. If a request is fired before the Hander is complete the initial request is killed and a new request is formed.

Create a new class in your project named DelayAutoCompleteTextView.java

public class DelayAutoCompleteTextView extends AutoCompleteTextView {
 
    private static final int MESSAGE_TEXT_CHANGED = 100;
    private static final int DEFAULT_AUTOCOMPLETE_DELAY = 750;
 
    private int mAutoCompleteDelay = DEFAULT_AUTOCOMPLETE_DELAY;
    private ProgressBar mLoadingIndicator;
 
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            DelayAutoCompleteTextView.super.performFiltering((CharSequence) msg.obj, msg.arg1);
        }
    };
 
    public DelayAutoCompleteTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    public void setLoadingIndicator(ProgressBar progressBar) {
        mLoadingIndicator = progressBar;
    }
 
    public void setAutoCompleteDelay(int autoCompleteDelay) {
        mAutoCompleteDelay = autoCompleteDelay;
    }
 
    @Override
    protected void performFiltering(CharSequence text, int keyCode) {
        if (mLoadingIndicator != null) {
            mLoadingIndicator.setVisibility(View.VISIBLE);
        }
        mHandler.removeMessages(MESSAGE_TEXT_CHANGED);
        mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_TEXT_CHANGED, text), mAutoCompleteDelay);
    }
 
    @Override
    public void onFilterComplete(int count) {
        if (mLoadingIndicator != null) {
            mLoadingIndicator.setVisibility(View.GONE);
        }
        super.onFilterComplete(count);
    }
}

Step 5 – Add the custom AutoCompleteTextView to your Activity’s layout file

In here I’ve used an X icon that allows the user to remove the current text inside the AutocompleteTextView. You can download this icon from here.

When you include your new custom component, use your own package name (e.g. com.yourthing.app)

Save this in your Activity’s layout, it contains the AutocompleteTextView and an ImageView containing the X icon for removing the text.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white">
 
    <YOUR_PACKAGE_NAME.DelayAutoCompleteTextView
        android:id="@+id/geo_autocomplete"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:imeOptions="flagNoExtractUi|actionSearch"
        android:inputType="textCapSentences"
        android:textColor="@color/header_text_color"
        android:background="@color/white"
        android:hint="@string/map_geo_search_input_hint"
        android:layout_gravity="center_vertical"
        android:layout_marginEnd="35dp"
        android:layout_marginRight="35dp"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:padding="10dp"
        android:dropDownWidth="fill_parent"
    />
 
   <ImageView
        android:id="@+id/geo_autocomplete_clear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_action_remove"
        android:contentDescription="@string/map_list_header_txt"
        android:layout_gravity="center_vertical|end"
        android:visibility="gone"
        android:layout_marginEnd="12dp"
        android:layout_marginRight="12dp"
	/>
</FrameLayout>

Step 6 – Assemble the components together

Add this code to the activity you wish to include the AutocompleteTextView and you should now have a Google Maps search box in your app!

        private Integer THRESHOLD = 2;
	private DelayAutoCompleteTextView geo_autocomplete;
	private ImageView geo_autocomplete_clear;
 
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.activity_geocoder);
 
	    geo_autocomplete_clear = (ImageView) findViewById(R.id.geo_autocomplete_clear);
 
	    geo_autocomplete = (DelayAutoCompleteTextView) findViewById(R.id.geo_autocomplete);
	    geo_autocomplete.setThreshold(THRESHOLD);
	    geo_autocomplete.setAdapter(new GeoAutoCompleteAdapter(this)); // 'this' is Activity instance
 
	    geo_autocomplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
	            @Override
	            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
	                GeoSearchResult result = (GeoSearchResult) adapterView.getItemAtPosition(position);
	                geo_autocomplete.setText(result.getAddress());
	            }
	        });
 
	    geo_autocomplete.addTextChangedListener(new TextWatcher() {
 
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
 
            }
 
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
 
            @Override
            public void afterTextChanged(Editable s) {
            	if(s.length() > 0)
            	{
            		geo_autocomplete_clear.setVisibility(View.VISIBLE);
            	}
            	else
            	{
            		geo_autocomplete_clear.setVisibility(View.GONE);
            	}
            }
        });
 
	    geo_autocomplete_clear.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				geo_autocomplete.setText("");
			}
	    });
 
	}

The majority of this codebase was developed by Alex Melnykov, I’ve just implemented the Geocoder search on top of this and added in an icon to clear the search box and format the results.

  • Andres Rodriguez

    hi the example its great, its just what i was looking for, but i have 2 errors in GeoAutoCompleteAdapter class, 1 is on “return resultList.get(index);” in the override method public GeoSearchResult getItem(int index){, and the other one is in the same class in the line “convertView = inflater.inflate(R.layout.geo_search_result_item, parent, false);” in the override method public View getView(int position, View convertView, ViewGroup parent) {,
    any idea who could it be?

    • Dima Rostopira

      There should be R.layout.geo_search_result. Just without the “_item”

    • For the “incompatible type” error in GeoAutoCompleteAdapter class, try to replace `return resultList.get(index);` with `return (GeoSearchResult) resultList.get(index);`

      And replace `resultList = (List) results.values;` with `resultList = (List) results.values;`

  • Carl

    please, set imports

    • نوره الشريف

      Hi , it maybe needs more ,

      import android.content.Context;

      import android.location.Address;

      import android.location.Geocoder;

      import android.view.LayoutInflater;

      import android.view.View;

      import android.view.ViewGroup;

      import android.widget.BaseAdapter;

      import android.widget.Filter;

      import android.widget.Filterable;

      import android.widget.TextView;

      import java.io.IOException;

      import java.util.ArrayList;

      import java.util.List;

  • Shreyash Sinha

    I have implement this on top of my Map. How to display a marker after searching a location here.

    • Nerob Space

      create a method in GeoSearchResult to get LatLng like below
      public LatLng getLatLng()
      {
      return latLng = new LatLng(address.getLatitude(), address.getLongitude());
      }

      after that create another method in your activity like below
      private void setSearchLocation(String address, double lat, double lng)
      {
      if (mMap != null)
      {
      if (currentLocationMarker != null)
      {
      currentLocationMarker.remove();
      }
      LatLng searchLatLng = new LatLng(lat, lng);
      MarkerOptions mOption = new MarkerOptions();
      mOption.position(searchLatLng).title(address).icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker));
      currentLocationMarker = mMap.addMarker(mOption);
      mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(searchLatLng, 15));
      }
      }

      call above method inside onItemClickListener and pass the required parameter like below
      // Set search location
      setSearchLocation(result.getAddress(), result.getLatLng().latitude, result.getLatLng().longitude);

      If anyone still thinking about in date to do this I posted for them. 🙂

  • Siva Prasad

    if (results != null && results.count > 0 error help pleas

  • Gerd Staudenmaier

    Hi, there is a further bug within GeoSearchResult.java.
    Here is the corrected version.

    public String getAddress() {

    String display_address = “”;

    for(int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
    display_address += address.getAddressLine(i) + ", ";
    }
    display_address = display_address.substring(0, display_address.length() – 2);
    return display_address;
    }

    The toString method is not required, but if used it seems to have a similar bug.

  • ukung zulfah

    Why error ?
    “myapplication.maps.DelayAutoCompleteTextView.setAdapter(android.widget.ListAdapter)’ on a null object reference”

  • Lee

    can i know how to handle the search button action ?

  • owl_letters

    And what should I do if I have non blocking API for fetching data and it uses callbacks? how can I delay findBooks return call? Thanks in advance,