Skip To Content

Add geocoding to your app

In this topic

The ArcGIS Runtime SDK for Android provides a Locator class that allows you to match addresses and place names to locations on the map (known as geocoding). The locator relies on Esri's geocoding web services to perform these functions.

Create the PlaceSearch project in Android Studio

Start by creating a new Android application project. These steps are essentially the same as covered in the Add a map to your app tutorial. For more information about the project set up steps, refer to the Add a map to your app tutorial.

  1. In the Android Studio Welcome screen, under Quick Start, click Start a new Android Studio project.
  2. In Application name, enter PlaceSearch. Optionally change the Company Name and Project Location if you want to, then click Next.New project dialog window in Android Studio
  3. Leave the default Phone and Tablet option selected, in the Minimum SDK drop-down list, choose API 15: Android 4.0.3, then click Next.
  4. Click Blank Activity, then click Next.
  5. Leave the default names for the activity, layout, title, and menu resource, and click Finish.

    A new default Android application project opens, showing a default layout.

Next you will add the Runtime Android SDK Android dependency.

Add the ArcGIS Runtime for Android AAR dependency

Here you will add the Runtime Android SDK dependency to your application module as an Android Archive (AAR) package.

  1. In the Android project view window, under Gradle Scripts, double-click build.gradle (Project: PlaceSearch). This opens the gradle build script for the project where you can add the Esri ArcGIS Maven repository URL as shown below:
    allprojects {
        repositories {
            jcenter()
            // add the esri arcgis maven repo
            maven {
                url 'http://esri.bintray.com/arcgis'
            }
        }
    
    }
  2. In the Android project view window, under Gradle Scripts, double-click build.gradle (Module: app).
  3. In the dependencies section of the script, add a new value compile 'com.esri.arcgis.android:arcgis-android:10.2.8-1':
    dependencies {
      compile fileTree(dir: 'libs', include: ['*.jar'])
      [...]
      compile 'com.esri.arcgis.android:arcgis-android:10.2.8-1' 
    }
  4. In the android section of the script, after defaultConfig, add the following values:
    packagingOptions{ 
      exclude 'META-INF/LGPL2.1' 
      exclude 'META-INF/LICENSE' 
      exclude 'META-INF/NOTICE' 
    }
  5. In the Android project view window, under manifests, double-click AndroidManifest.xml.
  6. In the AndroidManifest.xml file, add the following elements as children of the manifest element:
    <uses-feature android:glEsVersion="0x00020000"android:required="true" />
    <uses-permission android:name="android.permission.INTERNET" />
  7. In the Android Studio toolbar, click Sync Project with Gradle Files. Alternatively, when you see the Gradle files have changed since last project sync message at the top of the gradle script window, click Sync Now.

You have added a dependency to the Runtime Android SDK AAR to your app module. Next, you'll add code and a MapView.

Add a map and a graphics layer

In this step you will add a MapView to your layout. You will also add a graphics layer to display the results of your geocoding requests.

  1. In the Android project view window, under app, navigate to res > layout, then double-click activity_main.xml.

    Note that depending on the type of activity chosen and the version of Android Studio, your project may contain only one layout XML file, activity_main.xml, or multiple layout files, including content_main.xml; if so, open this file instead.

  2. At the bottom-left of the window, click Text to show the XML view of the layout.
  3. Select the entire TextView XML element and replace it with a MapView element as shown below:
    <com.esri.android.map.MapView
            android:id="@+id/map"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            mapoptions.MapType="Topo"
            mapoptions.center="34.056215, -117.195668"
            mapoptions.ZoomLevel="16"/>

    This map element has mapoptions attributes that tell the MapView to display initially using a default Topo map service layer, and to center the map at a specific set of coordinates, and zoom the map to a specific level.

  4. In the Android project view window, under app, navigate to java > com.esri.android.tutorials.placesearch, then double-click MainActivity to open the activity source code file.
  5. Add the following class variable declarations to the top of the MainActivity class:
    MapView mMapView;
    GraphicsLayer mLocationLayer;
    Point mLocationLayerPoint;
    String mLocationLayerPointString;

    These variables will hold references to the MapView you added to the layout, and also to the graphics layer which will hold geocode results.

  6. Android Studio will highlight in red the classes which must be imported to the class file. Place the cursor at the line with text highlighted in red and press ALT+Enter to resolve the symbol and import the com.esri.android.map.MapView, com.esri.android.map.GraphicsLayer and com.esri.core.geometry.Point classes to your activity class.
    Resolve symbol error message in Android Studio

    You should resolve symbols for any similar errors you get throughout this tutorial.

    Note:

    A complete list of imports is available at the end of this tutorial so you can check you have imported the classes correctly.

  7. In the OnCreate method, after the content view is set, set the reference to the MapView in the layout, and then create the feature service layer and graphics layer, and add both layers to the map:

    mMapView = (MapView)findViewById(R.id.map);
    mLocationLayer = new GraphicsLayer();
    mMapView.addLayer(mLocationLayer);
  8. After the code you just added, add the code below to set a listener that will be called when the MapView is initialized. You will check this in a later step to see if the map is ready for a user to make queries.

    mMapView.setOnStatusChangedListener(new OnStatusChangedListener() {
      public void onStatusChanged(Object source, STATUS status) {
        if ((source == mMapView) && (status == STATUS.INITIALIZED)) {
          mIsMapLoaded = true; 
        } 
      } 
    });

You now have an app that contains a map with a graphics layer. You can run the app and test your code is working so far—it should simply display the topological map. Next, you will add controls to the action bar to allow a place or address to be entered.

Allow users to enter an address

Now you will add Android Views to the action bar of your layout to allow a user to enter text specifying an address or place to search for.

  1. In the Android project view window, under app, navigate to res > values, then double-click strings.xml.
  2. Add the following string resources:
    <string name="action_settings">Settings</string>
    <string name="action_search">Place Search</string>
    <string name="search_hint">Search address or place</string>
    <string name="menu_search">Search</string>
    
    <string name="address_search">Searching for Address</string>
    <string name="addressSearchFailed">Address search failed</string>
    <string name="noResultsFound">No results found</string>

    These strings will be displayed to the user in the action bar user interface, and later on if there are problems finding results. You will use them in the steps below.

  3. In the Android project view window, navigate to app > res, then right-click layout.
  4. Choose New > Layout resource file.
  5. In the New Resource File dialog, enter the following:
    • File name: search_layout.xml
    • Root element:: RelativeLayout
    • Source set: main
    • Directory name: layout
  6. Click OK.

    This adds a new XML resources directory and resource file to your app module.

  7. In the Text view of the search_layout.xml window, add the following element to the existing RelativeLayout element:

    <ImageButton
            android:id="@+id/searchButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:background="#000000"
            android:contentDescription="@string/menu_search"
            android:onClick="onSearchButtonClicked"
            android:src="@android:drawable/ic_menu_search" />
    
        <EditText
            android:id="@+id/searchText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/searchButton"
            android:hint="@string/search_hint"
            android:inputType="text"
            android:minWidth="150dp" >
    
            <requestFocus />
        </EditText>

    The search button will run a method called onSearchButtonClicked. You will add this method later.

  8. In the Android project view window, under app, navigate to res > menu, then double-click menu_main.xml.
  9. Replace the entire menu element with the following elements:
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          tools:context=".MainActivity">
      <item
          android:id="@+id/action_search"
          app:actionLayout="@layout/search_layout"
          android:title="@string/action_search"
          android:icon="@android:drawable/ic_menu_search"
          app:showAsAction="ifRoom|collapseActionView" />
    </menu>

    This defines the search option in the action bar should refer to the layout you added in the previous step.

  10. In the Android project view window, under app, navigate to java > com.esri.android.tutorials.placesearch, then double-click MainActivity to open the activity source code file.
  11. Add the following class variable declaration to the top of the MainActivity class:
    EditText mSearchEditText;

    This variable will hold a reference to the EditText in the action bar where a user will enter a search string.

  12. In the onCreateOptionsMenu method, add the following lines of code after the getMenuInflater.inflate(R.menu.menu_main, menu) line, and before the return true line.
    View searchRef = menu.findItem(R.id.action_search).getActionView();
    mSearchEditText = (EditText) searchRef.findViewById(R.id.searchText);
    
    mSearchEditText.setOnKeyListener(new View.OnKeyListener() {
    
      @Override
      public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
        if(keyCode == KeyEvent.KEYCODE_ENTER){
          onSearchButtonClicked(mSearchEditText);
          return true;
        }
        return false;
      }
    });
    This sets the variables you added previously to reference the items in the options menu, when the menu layout is first shown; you will use these references in the following step.
  13. In the onOptionsItemSelected method, replace the entire method body with the following code:
    int id = item.getItemId();
    if (id == R.id.action_search) {
      return true;
    }
    return super.onOptionsItemSelected(item);

    This code indicates that no action needs to be taken when the option menu item is shown; instead, the place search is performed when the user clicks the Search button.

  14. Now add the onSearchButtonClicked method to your MainActivity class.
    public void onSearchButtonClicked(View view){
      InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
      inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
      String address = mSearchEditText.getText().toString();
      executeLocatorTask(address);
    }

    This hides the Android soft input keyboard (the keyboard shown on screen) and begins the place search when the user clicks the search button.

Define a task to run a query and display the results

Performing a place search involves a network call to the geocoding service. In order for the app UI thread to remain responsive while the place search is being executed, you will create an AsyncTask to perform the work on a background thread.

Learn more about Keeping your app responsive.

  1. In the MainActivity class, create a function called executeLocatorTask using the code below:
    private void executeLocatorTask(String address) {
            // Create Locator parameters from single line address string
            LocatorFindParameters findParams = new LocatorFindParameters(address);
    
            // Use the centre of the current map extent as the find location point
            findParams.setLocation(mMapView.getCenter(), mMapView.getSpatialReference());
    
            // Calculate distance for find operation
            Envelope mapExtent = new Envelope();
            mMapView.getExtent().queryEnvelope(mapExtent);
            // assume map is in metres, other units wont work, double current envelope
            double distance = (mapExtent != null && mapExtent.getWidth() > 0) ? mapExtent.getWidth() * 2 : 10000;
            findParams.setDistance(distance);
            findParams.setMaxLocations(2);
    
            // Set address spatial reference to match map
            findParams.setOutSR(mMapView.getSpatialReference());
    
            // Execute async task to find the address
            new LocatorAsyncTask().execute(findParams);
            mLocationLayerPointString = address;
        }

    This function creates a LocatorFindParameters that is required to call the locator, specifies the search string entered by the user, and also sets a parameter to restrict the locator to search an area centered on the current extent of the map. Lastly, it creates a LocatorAsyncTask and calls execute—you will now create this LocatorAsyncTask class.

  2. In the MainActivity class, create a private subclass called LocatorAsyncTask that inherits from AsyncTask. run the query task off the UI thread:
    private class LocatorAsyncTask extends AsyncTask<LocatorFindParameters, Void, List<LocatorGeocodeResult>> {
      private Exception mException;
    }

    Note that you have also defined a class variable that holds an exception. This may be set in the doInBackground method on the background thread and later checked in the onPostExecute method on the UI thread. You should see a message asking you to implement the abstract method doInBackground.

  3. Add the following code to the LocatorAsyncTask to define the doInBackground method.

    @Override
    protected List<LocatorGeocodeResult> doInBackground(LocatorFindParameters... params) {
      mException = null;
      List<LocatorGeocodeResult> results = null;
      Locator locator = Locator.createOnlineLocator();
      try {
        results = locator.find(params[0]);
      } catch (Exception e) {
        mException = e;
      }
      return results;
    }

    Note that this method receives a LocatorFindParameters parameter; this contains the parameters you prepared in the previous step that are required by the locator service task. This method will be called on a background thread. Its results will be passed to the onPostExecute method that you will add next. A class variable is set if an exception is thrown from the call to the locator.

  4. Add the following code to the LocatorAsyncTask to define the onPostExecute method.

    protected void onPostExecute(List<LocatorGeocodeResult> result) {  
    if (mException != null) {
                    Log.w("PlaceSearch", "LocatorSyncTask failed with:");
                    mException.printStackTrace();
                    Toast.makeText(MainActivity.this, getString(R.string.addressSearchFailed), Toast.LENGTH_LONG).show();
                    return;
                }
    
                if (result.size() == 0) {
                    Toast.makeText(MainActivity.this, getString(R.string.noResultsFound), Toast.LENGTH_LONG).show();
                } else {
                    // Use first result in the list
                    LocatorGeocodeResult geocodeResult = result.get(0);
    
                    // get return geometry from geocode result
                    Point resultPoint = geocodeResult.getLocation();
                    // create marker symbol to represent location
                    SimpleMarkerSymbol resultSymbol = new SimpleMarkerSymbol(Color.RED, 16, SimpleMarkerSymbol.STYLE.CROSS);
                    // create graphic object for resulting location
                    Graphic resultLocGraphic = new Graphic(resultPoint, resultSymbol);
                    // add graphic to location layer
                    mLocationLayer.addGraphic(resultLocGraphic);
    
                    // create text symbol for return address
                    String address = geocodeResult.getAddress();
                    TextSymbol resultAddress = new TextSymbol(20, address, Color.BLACK);
                    // create offset for text
                    resultAddress.setOffsetX(-4 * address.length());
                    resultAddress.setOffsetY(10);
                    // create a graphic object for address text
                    Graphic resultText = new Graphic(resultPoint, resultAddress);
                    // add address text graphic to location graphics layer
                    mLocationLayer.addGraphic(resultText);
    
                    mLocationLayerPoint = resultPoint;
    
                    // Zoom map to geocode result location
                    mMapView.zoomToResolution(geocodeResult.getLocation(), 2);
                }
    }

    Note that this method receives a list of LocatorGeocodeResult objects; this value was created in the doInBackground method you added previously. In this onPostExecute method, the location of the first result from the search is converted into a Graphic and added to the map, along with an additional Graphic which contains a result label drawn with a TextSymbol. Finally, the map extent is updated to show the result graphic.

You can now test your place search app is working.

Run the application

To test your app, you should either have a connected device ready for debugging, or have a set up and started an emulator. To find our more, refer to the Android documentation on Using the emulator or Using hardware devices.

  1. In the Android Studio toolbar, click Run.
  2. In the Choose Device dialog, select the device or emulator you wish to use, then click OK
  3. When the app opens on your device, it shows a topological map.
  4. In the action bar of the app, tap the options overflow, and select one of the query options available.

    The map updates to show blue symbols for the features that satisfy that query.

    Place Search app showing query and results

That's it, you have built an address and place search app with the ArcGIS Runtime SDK for Android.

See an enhanced version of the PlaceSearch sample on the developers.arcgis.com website—this demonstrates how you can add a progress dialog while your search is executing. Fork the samples repo on GitHub to work with the sample.

Tip:

The complete list of imports for your project should be:

import android.content.Context;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;  // May vary depending Android Studio version and options chosen
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;

import com.esri.android.map.GraphicsLayer;
import com.esri.android.map.MapView;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Point;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.SimpleMarkerSymbol;
import com.esri.core.symbol.TextSymbol;
import com.esri.core.tasks.geocode.Locator;
import com.esri.core.tasks.geocode.LocatorFindParameters;
import com.esri.core.tasks.geocode.LocatorGeocodeResult;