Hands On

Search for Points of Interest in an Android Mobile Application

By Nic Raboy | 20 December 2018

I had recently written a tutorial that demonstrated geocoding addresses into latitude and longitude coordinates, then placing those coordinates on a HERE map as a marker in Android. This previous tutorial titled, Geocoding Addresses with HERE in an Android Mobile Application, focused on the HERE Geocoder API. What if we didn’t know the address that we wanted, but instead a place or point of interest?

We’re going to see how to use the HERE Places API to search for points of interest and display them on a map within an Android mobile application.

To get a better idea of what we hope to accomplish, take a look at the following animated image:

android-places-search

The above image might look familiar in the sense that a lot of it was taken from the geocoding tutorial. We are accepting a search query and the HERE API will take that query and find possible matches which we then display as markers on the map.

Searching with the HERE Places API for Android

The first of the two parts of this tutorial is centered around searching. You’ll notice that we won’t be going into any detail around configuration of HERE in Android. If you’re interested in configuring HERE in Android, check out my previous tutorial titled, Getting Started with HERE Maps in an Android Application.

Within our Activity, make sure the following variables exist in some form:

private Map map = null;
private MapFragment mapFragment = null;
private EditText editText = null;
private ArrayList<MapObject> markers = null;

For the first part, the markers list will be important to us. The plan will be to clear the markers list every time we search and populate it with new objects from the search results.

Take a look at the following search method:

public void search(String query) {
    if(!markers.isEmpty()) {
        map.removeMapObjects(markers);
        markers.clear();
    }
    try {
        GeoCoordinate tracy = new GeoCoordinate(37.7397, -121.4252);
        DiscoveryRequest request = new SearchRequest(query).setSearchCenter(tracy);
        request.setCollectionSize(5);
        ErrorCode error = request.execute(new ResultListener<DiscoveryResultPage>() {
            @Override
            public void onCompleted(DiscoveryResultPage discoveryResultPage, ErrorCode error) {
                if (error != ErrorCode.NONE) {
                    Log.e("HERE", error.toString());
                } else {
                    for(DiscoveryResult discoveryResult : discoveryResultPage.getItems()) {
                        if(discoveryResult.getResultType() == DiscoveryResult.ResultType.PLACE) {
                            PlaceLink placeLink = (PlaceLink) discoveryResult;
                            MapMarker marker = new MapMarker();
                            marker.setCoordinate(placeLink.getPosition());
                            markers.add(marker);
                        }
                    }
                    map.addMapObjects(markers);
                }
            }
        });
        if( error != ErrorCode.NONE ) {
            Log.e("HERE", error.toString());
        }
    } catch (IllegalArgumentException ex) {
        Log.e("HERE", ex.getMessage());
    }
}

There is a lot of code in the above snippet, but not a whole lot going on. After the markers list is cleared and the map is cleaned, we provide a set of coordinates to search around. We’ve hardcoded those coordinates, but in a more extravagant scenario, we’d probably use the coordinates from the device GPS. For the result, we’re saying we only want five points of interest to be returned.

When the request is executed with our position and the search query, we wait for the results. When we have the results, we loop through them and check to make sure they are a place versus something else. If we have a place, we can create a new marker with the position and add it to the map.

In the search method, the HERE Places API handles all of the heavy lifting for us.

Responding to Key Events with the Android Key Listener

With the not-so complicated stuff out of the way, we can focus on listening for key events which is also not at all complicated. The listener has nothing to do with HERE and is functionality of the Android SDK.

Take a look at our onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.mapfragment);
    editText = (EditText) findViewById(R.id.query);

    markers = new ArrayList<MapObject>();

    editText.setOnKeyListener(new View.OnKeyListener() {
        public boolean onKey(View view, int keyCode, KeyEvent keyevent) {
            if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                search(editText.getText().toString());
                editText.setText("");
                return true;
            }
            return false;
        }
    });

    mapFragment.init(new OnEngineInitListener() {
        @Override
        public void onEngineInitializationCompleted(OnEngineInitListener.Error error) {
            if (error == OnEngineInitListener.Error.NONE) {
                map = mapFragment.getMap();
                map.setCenter(new GeoCoordinate(37.7397, -121.4252, 0.0), Map.Animation.NONE);
                map.setZoomLevel((map.getMaxZoomLevel() + map.getMinZoomLevel()) / 2);
            }
        }
    });
}

As you can see in the above code, we have our mapFragment as well as our editText loaded from the XML view. To get more perspective on what our XML looks like, it looks like the following:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/query"
        android:hint="Query..."
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:inputType="text"/>

    <fragment
        class="com.here.android.mpa.mapping.MapFragment"
        android:id="@+id/mapfragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

In the above XML, we define both the EditText and Fragment giving them an id that can be used in the Java. Looking back at our Java code, we can look at the actual listener:

editText.setOnKeyListener(new View.OnKeyListener() {
    public boolean onKey(View view, int keyCode, KeyEvent keyevent) {
        if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
            search(editText.getText().toString());
            editText.setText("");
            return true;
        }
        return false;
    }
});

In the listener, we are listening for key press events, but we want those events to be the enter or the done key. When those keys are pressed, we can obtain the text in the field and use it as our query string for the search. After searching we can empty the field to be searched again.

Just like with the searching aspect of this tutorial, the listening of key events wasn’t any more difficult.

Conclusion

You just saw how to search for places such as restaurants, gas stations, etc., using the HERE Places API in an Android application. Being able to search for points of interest without knowing their position or address is a powerful feature that HERE offers.

As previously mentioned, if you needed some help configuring your application to use HERE, check out my previous tutorial on the topic. In case you’re interested in taking this tutorial to the next level, check out my tutorial titled, Positions to Addresses with Gesture Events and HERE in Android, which should give you some new perspective on the things you can accomplish.