Hands On

Real-Time Interaction with HERE Maps Between Android and JavaScript with Socket.io

By Nic Raboy | 16 April 2019

If you’ve been keeping up with my content, you might remember that about a week ago I had written a tutorial around real-time interaction using Socket.io and a HERE map. This tutorial titled, Real-Time Interaction Between Maps with Socket.io and JavaScript, demonstrated how Socket.io could be used to send client interactions with a map to a server and then broadcasted in real-time to all other connected clients, showing the results on the screen.

We’re going to take things a step further in this tutorial. Instead of web browser to web browser communication, we’re going to introduce Android communication so that way as a user interacts with a map, whether it be in a web browser or on an Android device, those interactions get broadcasted to all similar platforms.

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

android-here-socketio

Clicking an area on the Android map will add a marker to the Android map as well as all connected web browser and Android clients. Likewise, clicking an area on the web browser map will do the same.

The code for the Socket.io server and JavaScript client that we saw in the other tutorial have not changed for this tutorial. For this reason, we won’t be exploring these again even though they’ll be heavily referenced. Definitely consult my previous tutorial if you haven’t already.

Configuring an Android Application to use Socket.io

Using Socket.io in Android with Java isn’t too different than using it with JavaScript. For the most part the APIs are the same. However, being that this is Android, there are some project level configurations that must happen first.

If you’ve never used HERE with Android, I suggest you check out my previous tutorial titled, Getting Started with HERE Maps in an Android Application. We won’t be walking through the steps to configure HERE in this tutorial, only how to use it with Socket.io.

Open your project’s build.gradle file because we need to have Gradle import our Socket.io dependency. Within this file, include the following:

dependencies {
    // ...
    implementation ('io.socket:socket.io-client:1.0.0') {
        exclude group: 'org.json', module: 'json'
    }
}

You’ll want to include the library in your dependencies block without removing the other dependencies that are already in there. Once we have Socket.io as part of our project through Gradle, we need to make some changes to the project manifest file. Open the project’s AndroidManifest.xml file and make the following changes:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.raboy">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:hardwareAccelerated="true"
        android:usesCleartextTraffic="true">
        <meta-data android:name="com.here.android.maps.appid" android:value="APP_ID_HERE"/>
        <meta-data android:name="com.here.android.maps.apptoken" android:value="APP_CODE_HERE"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Much of the AndroidManifest.xml file is based on configurations that you’ve probably made for the HERE Android SDK. What we’re looking for in particular when it comes to Socket.io is the following line:

android:usesCleartextTraffic="true"

It took me quite a while to figure out that the above line was needed. In older versions of Android it’s not necessary to explicitly allow cleartext traffic, but in more modern versions, like the Android Pie simulator that I’m using, it is a requirement, otherwise our socket connections will never establish.

At this point in time we’re actually ready to start using Socket.io with HERE in our Android application.

Sending Data and Responding to Events Visually with a HERE Map

Before we start throwing in code, let’s understand how our application is going to work. Essentially we have two parts when it come to Socket.io.

When we click on the map, a gesture event will trigger which will allow us to convert the pixel information into coordinate information. Using those coordinates we will send them to our Socket.io server. That is the first part.

The second part will be around receiving coordinate information. The server will be sending all connected clients, both JavaScript and Android, broadcasts as they are received. In Android we need to listen for them and take the position data in them and turn them into markers.

So let’s get up to speed when it comes to our Android code. In the MainActivity.java file you probably have something like this:

public class MainActivity extends AppCompatActivity {

    private Map map = null;
    private SupportMapFragment mapFragment = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapfragment);
        mapFragment.init(new OnEngineInitListener() {
            @Override
            public void onEngineInitializationCompleted(OnEngineInitListener.Error error) {
                if (error == OnEngineInitListener.Error.NONE) {
                    map = mapFragment.getMap();
                    map.setCenter(new GeoCoordinate(37.7394, -121.4366, 0.0), Map.Animation.NONE);
                    map.setZoomLevel(12);
                }
            }
        });
    }

}

As part of our first step, we are going to establish a connection to the Socket.io server that we built with Node.js in the previous tutorial. First create a private variable like the following:

private Socket socket;

It is up to you where we actually try to establish a connection, but wherever we decide, we’ll need to catch any possible exceptions. Let’s modify our code to look like the following:

public class MainActivity extends AppCompatActivity {

    private Map map = null;
    private SupportMapFragment mapFragment = null;
    private Socket socket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            socket = IO.socket("http://10.0.2.2:3000");
            socket.connect();
            mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapfragment);
            mapFragment.init(new OnEngineInitListener() {
                @Override
                public void onEngineInitializationCompleted(OnEngineInitListener.Error error) {
                    if (error == OnEngineInitListener.Error.NONE) {
                        map = mapFragment.getMap();
                        map.setCenter(new GeoCoordinate(37.7394, -121.4366, 0.0), Map.Animation.NONE);
                        map.setZoomLevel(12);
                    }
                }
            });
        } catch (URISyntaxException e) {
            Log.e("HERE", e.getMessage());
        }
    }

}

Notice that we’ve added two particular lines in the above code. In my circumstance, my Socket.io server is running on my local computer and I’m using my Android simulator. For this reason, I cannot connect my simulator to localhost, so I have to use 10.0.2.2 instead. After defining the server information, then we can try to connect.

Since we’ve already got a functional JavaScript application that is sending marker information, let’s listen for that information as part of our next step.

Within the onEngineInitializationCompleted method, add the following:

socket.on("marker", new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        try {
            JSONObject position = (JSONObject) args[0];
            MapMarker defaultMarker = new MapMarker();
            defaultMarker.setCoordinate(new GeoCoordinate(position.getDouble("lat"), position.getDouble("lng"), 0.0));
            map.addMapObject(defaultMarker);
        } catch (JSONException e) {
            Log.e("HERE", e.getMessage());
        }
    }
});

In the above code, we are listening for marker events or labels, which is what the server is configured to send. When we receive one of those events, we can convert it into JSON and extract the lat and lng values to be used in a MapMarker. Once we have our MapMarker we can add it to the map.

If the server is storing a bunch of markers, when you launch your Android application, they should all start to populate.

We want Android to be able to send marker data as well, not just JavaScript. After our marker event, add the following:

mapFragment.getMapGesture().addOnGestureListener(new MapGesture.OnGestureListener.OnGestureListenerAdapter() {
    @Override
    public boolean onTapEvent(PointF p) {
        GeoCoordinate position = map.pixelToGeo(p);
        try {
            JSONObject pos = new JSONObject();
            pos.put("lat", position.getLatitude());
            pos.put("lng", position.getLongitude());
            socket.emit("marker", pos);
        } catch (JSONException e) {
            Log.e("HERE", e.getMessage());
        }
        return false;
    }
});

What we’re saying in the above code is that when a tap event occurs, calculate the coordinates based on the pixels and add them to a JSON object. We then send that JSON object to the Socket.io server to be broadcasted.

Conclusion

You just saw how to use Socket.io with Android to send tap information to other connected Android and JavaScript devices to be converted into markers on a map. By doing this, a real-time interactive experience is created HERE that is carried between platforms.

If you need help with the JavaScript side of things, I encourage you to check out my previous tutorial which focuses on the Socket.io server and JavaScript web application.