Skip To Content ArcGIS for Developers Sign In Dashboard

Overview

You will learn: how to find an address or place using the ArcGIS World Geocoding Service.

With the ArcGIS Runtime SDK for Android you can easily find places and addresses all around the world. The process of matching locations on the map to an address is referred to as geocoding. Your user can specify a location of interest or street address and see it on the map.

Locating addresses is a complex topic. Refer to Search for places (geocoding) if you would like to read more about the process and options.

This tutorial is a bit complex because you will define a user interface to handle user interaction. This demonstrates one of many possible methods for requesting user input. The method used here places the search widget on the Action Bar, as is best practice in Material Design.

In this tutorial, you will build a simple search app that can find places in and around the Los Angeles area.

Before you begin

Make sure you have installed the latest version of Android Studio.

Reuse the starter project

If you have completed the Create a starter app tutorial, then copy the project into a new empty folder. Otherwise, download and unzip the project solution. Open, run, and verify the map displays in the device simulator.

Steps

Prompt the user for input

  1. Define a UI element to ask the user to enter an address. To place a menu item on the action bar, create an options menu. Add a new menu resource file to define your menu:

    1. In the Project view, right-click app and choose New > Android resource file.
    2. Set the File name to options_menu.
    3. In Resource type choose Menu.
    4. Leave the other options as default (Source set as main, Directory name as menu) and click OK.
  2. In the Text tab of the new menu file you created, replace the existing menu item with that in the following code.

    <?xml version="1.0" encoding="utf-8"?>
    <!-- *** ADD *** replace the existing menu element -->
    <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
         <item
         android:id="@+id/search"
         android:title="@string/search_title"
         android:icon="@android:drawable/ic_menu_search"
         app:actionViewClass="android.widget.SearchView"
         app:showAsAction="always" />
    </menu>
    
  3. The title is showing in red, indicating the definition for the search title is unknown. To fix this, open the existing resource file app > res > values > strings.xml. Add new elements inside the <resources> section to define this string, and other strings you'll use later on:

    <!-- *** ADD *** -->
    <string name="search_title">Search</string>
    <string name="search_hint">Find an address</string>
    <string name="nothing_found">Nothing found for</string>
    
  4. Define a searchable configuration. Because you are using the Android platform's SearchManager, it requires this configuration. Define a new XML resource:

    1. In the Project view, right-click app and choose New > Android resource file.
    2. Set the File name to searchable.
    3. In Resource type choose XML.
    4. Set the Root element to searchable.
    5. Leave the other options as default (Source set as main, Directory name as xml) and click OK.
    6. Select the Text tab to display the XML code in the editor, and add the following XML elements:
     <?xml version="1.0" encoding="utf-8"?>
     <searchable xmlns:android="http://schemas.android.com/apk/res/android"
         android:label="@string/app_name"
         android:hint="@string/search_hint">
     </searchable>
    
  5. Update the android manifest to indicate the use of search manager and the search intent. The Android manifest file is located in the Project view under app > manifests > AndroidManifest.xml. Update the existing <activity> element as follows:

    <!-- *** ADD *** update this element to add the launchMode attribute -->
    <activity android:name=".MainActivity"
      android:launchMode="singleTop">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
             <!-- *** ADD *** -->
             <intent-filter>
                 <action android:name="android.intent.action.SEARCH" />
             </intent-filter>
             <meta-data android:name="android.app.searchable"
                 android:resource="@xml/searchable"/>
    

The next part of the project is to write the code to find a geocode match when the user enters a request inside this input and then taps search.

Search for an address

  1. The SearchView is a widget provided by the Android platform that you placed on the action bar menu. You will access the widget throughout your code, which will require a member variable reference on the MainActivity class. Open the file app > java > {your.package.name} > MainActivity.java and add new private variables to hold a reference to the search widget, and to other resources (GraphicsOverlay, LocatorTask, GeocodeParameters) you'll require later in this tutorial.

    private SearchView mSearchView = null;
    private GraphicsOverlay mGraphicsOverlay;
    private LocatorTask mLocatorTask = null;
    private GeocodeParameters mGeocodeParameters = null;
    
  2. Continue updating MainActivity.java by adding the following new method after the existing onCreate() method. This new method, onCreateOptionsMenu(), is an Android framework method used to specify the options menu for the activity. In this method you will add code to create the search widget and add it to the action bar.

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        MenuItem searchMenuItem = menu.findItem(R.id.search);
        if (searchMenuItem != null) {
            mSearchView = (SearchView) searchMenuItem.getActionView();
            if (mSearchView != null) {
                SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
                mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
                mSearchView.setIconifiedByDefault(false);
            }
        }
        return true;
    }
    
  3. In response to user input, the search widget sends an Intent to your main activity. You set this up in the searchable XML and the Android manifest earlier. Add code to receive this Intent, verify it is a search action, and call your search method.

     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
             queryLocator(intent.getStringExtra(SearchManager.QUERY));
         }
     }
    
  4. Add a new private method, named queryLocator(), to your main activity class to perform the geocode search given a search string.

     private void queryLocator(final String query) {
         if (query != null && query.length() > 0) {
             mLocatorTask.cancelLoad();
             final ListenableFuture<List<GeocodeResult>> geocodeFuture = mLocatorTask.geocodeAsync(query, mGeocodeParameters);
             geocodeFuture.addDoneListener(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         List<GeocodeResult> geocodeResults = geocodeFuture.get();
                         if (geocodeResults.size() > 0) {
                             displaySearchResult(geocodeResults.get(0));
                         } else {
                             Toast.makeText(getApplicationContext(), getString(R.string.nothing_found) + " " + query, Toast.LENGTH_LONG).show();
                         }
                     } catch (InterruptedException | ExecutionException e) {
                         // ... determine how you want to handle an error
                     }
                     geocodeFuture.removeDoneListener(this); // Done searching, remove the listener.
                 }
             });
         }
     }
    
  5. Show a graphic on the map when a result is located. Add a new private method to your main activity class that takes a GeocodeResult and displays it on the map:

     private void displaySearchResult(GeocodeResult geocodedLocation) {
         String displayLabel = geocodedLocation.getLabel();
         TextSymbol textLabel = new TextSymbol(18, displayLabel, Color.rgb(192, 32, 32), TextSymbol.HorizontalAlignment.CENTER, TextSymbol.VerticalAlignment.BOTTOM);
         Graphic textGraphic = new Graphic(geocodedLocation.getDisplayLocation(), textLabel);
         Graphic mapMarker = new Graphic(geocodedLocation.getDisplayLocation(), geocodedLocation.getAttributes(),
                 new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.SQUARE, Color.rgb(255, 0, 0), 12.0f));
         ListenableList allGraphics = mGraphicsOverlay.getGraphics();
         allGraphics.clear();
         allGraphics.add(mapMarker);
         allGraphics.add(textGraphic);
         mMapView.setViewpointCenterAsync(geocodedLocation.getDisplayLocation());
     }
    
  6. Before you can run a geocode search the LocatorTask and GeocodeParameters must be initialized. This should only be done once. The initialization code is separated into its own method of the main activity that is called when the app starts.

    private void setupLocator() {
        String locatorService = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer";
        mLocatorTask = new LocatorTask(locatorService);
        mLocatorTask.addDoneLoadingListener(() -> {
            if (mLocatorTask.getLoadStatus() == LoadStatus.LOADED) {
                mGeocodeParameters = new GeocodeParameters();
                mGeocodeParameters.getResultAttributeNames().add("*");
                mGeocodeParameters.setMaxResults(1);
                mGraphicsOverlay = new GraphicsOverlay();
                mMapView.getGraphicsOverlays().add(mGraphicsOverlay);
            } else if (mSearchView != null) {
                mSearchView.setEnabled(false);
            }
        });
        mLocatorTask.loadAsync();
    }
    
  7. Call the new set up method to prepare to accept user search requests. Revisit the onCreate() method and call setupLocator() after the call to setupMap():

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMapView = findViewById(R.id.mapView);
        setupMap();
        // *** ADD ***
        setupLocator();
    
  8. Press Control-R to run the app in the simulator.

Congratulations, you're done!

Your app should run and the action bar should display the search widget in the open (non-iconic) state with the placeholder text Find an address prompting you to enter an address. When you type an address into the search widget and press ENTER the map should re-center on a red square with a text label displaying the geocoded address. Compare your solution with our completed solution project.

Challenge

Explore graphics

Try different graphics rendering for the displaySearchResult() method - change the text and graphic size and color.

Multiple results

Try setting setMaxResults to a number larger than one and show the user the possible matches. Can you come up with a UI that allows the user to select a possible match?

Geocoding parameters

Can you discover other geocoding parameters available to the locator task? For example, limit the search area to the visible extent of the map view.

Reverse geocode

The LocatorTask class has a ReverseGeocodeAsync method that returns an interpolated address (string) for a location (map point). Experiment with code that uses a click on the map to perform a reverse geocode.