Display a driver's reported location from a server

Let's say a driver is using turn-by-turn directions and the driver's mobile app will report the current location and ETA to a server.

In this example, from a second mobile app, we monitor the driver's location and ETA, and display that on a map.

You will need to provide your own server for this. In our example, we use WebSockets to connect to the server in order to receive immediate updates as the location or ETA changes. In your implementation, you might choose to use WebSockets or some other similar technology to meet this need.

Before you do this:

This is an advanced example, with a lot of moving parts (two client apps and one server). If you haven't already, we suggest getting a feel for the SDK with some of the more basic examples before trying this one.

First make sure you have turn-by-turn navigation up and running in your app. If you haven't done this yet, here's how .

You will also need a server that can receive and send data for this example. We provide a reference server that you can use for testing this, but you will probably want to replace it with your own server implementation later.

If you use our reference server, you will also need to implement a driver app that can report the current location and ETA to a server.

If you've done all that, then by all means, read on!

We can use a third-party library (which is already included in the TallyGo SDK) to help us with websockets. For this example we will use OkHttp .

Let's set up some constants for the two kinds of events we will be handling:

public enum EventType {
    ETA,
    CURRENT_LOCATION

    public static EventType fromJson(String json) {
        for (EventType eventType: values()) {
            if (eventType.name().toLowerCase().equals(json)) {
                return eventType;
            }
        }
        return null;
    }
}

You will also need a TGMapView , which we will setup in our onCreate() method. Here R.layout.display_driver_report_activity is a custom layout that we will define and R.id.fl_map_fragment_holder is a FrameLayout that we will use to hold the fragment

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.display_driver_report_activity);
    TGLauncher.startBaseSupportMapFragment(this, R.id.fl_map_fragment_holder);
}

Here is our custom layout called R.layout.display_driver_report_activity :

<?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="match_parent">

<FrameLayout
    android:id="@+id/fl_map_fragment_holder"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

<TextView
    android:id="@+id/tv_eta_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center_horizontal"
    android:padding="16dp"
    android:layout_margin="16dp"
    android:textSize="24sp"
    android:background="#FFFFFF"
    android:visibility="gone"
    />

</FrameLayout>

Now let's set up a WebSockets connection and parsing for any data we receive through it. We assume the data will be in JSON format.

private WebSocket websocket;

private void startWebSocket() {
    OkHttpClient socketClient = new OkHttpClient();
    Request request = new Request.Builder().url("ws://localhost:3200/").build();
    websocket = socketClient.newWebSocket(request, socketListener);
    socketClient.dispatcher().executorService().shutdown();
}

private final WebSocketListener socketListener = new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        Timber.i("WebSocket opened");
    }
    @Override
    public void onMessage(WebSocket webSocket, final String text) {
        //handle our messages on the main thread
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                onSocketReceiveMessage(text);
            }
        });
    }
    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
        Timber.i("WebSocket closing");
    }
    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        Timber.e("WebSocket failure");
    }
};
Notice if not using emulator for development:

localhost will not be accessible if you are not developing on the emulator. As long as the machine running your server and your development Android device are on the same wireless network it should be as simple as swapping localhost for your LAN ip address (which can be found in network settings)

WebSockets are a persistent connection, so we should have a method for disconnecting when we need to.

private void stopWebSocket() {
    websocket.close(4000, null);
}

In order to prevent the socket staying open when the user navigates away from the application we start and stop the socket as needed.

@Override
protected void onStart() {
    super.onStart();
    startWebSocket();
}

@Override
protected void onStop() {
    super.onStop();
    stopWebSocket();
}

So, we have connected to the server and parsed the JSON data, but we still need to do something with that data. Let's separate that out into the two kinds of events we are handling:

private void onSocketReceiveMessage(String text) {
    if (text == null) {
        return;
    }
    try {
        JSONObject json = new JSONObject(text);
        EventType eventType = EventType.fromJson(json.getString("event_type"));
        JSONObject payload = json.getJSONObject("payload");
        if (eventType == null || payload == null) {
            return;
        }

        switch (eventType) {
            case ETA:
                handleEtaPayload(payload);
                break;
            case CURRENT_LOCATION:
                handleLocationPayload(payload);
                break;
        }
    } catch (JSONException ignored) {}
}

The first type is the current location event. This places a marker on the map to show the driver's current location, and scrolls the map to keep it in focus:

private Marker driverLocationMarker;

private void handleLocationPayload(JSONObject payload) throws JSONException {
    double lat = payload.getDouble("latitude");
    double lon = payload.getDouble("longitude");
    LatLng location = new LatLng(lat, lon);
    updateDriverLocation(location);
}

private void updateDriverLocation(LatLng location) {
    if (getTGMap() == null) {
        return;
    }
    if (driverLocationMarker == null) {
        //create marker
        Icon icon = TGIconFactory.getInstance().getDefaultIcon(getBaseContext(), Color.BLUE);
        MarkerOptions options = new MarkerOptions().position(location).icon(icon).setTitle("Driver Location");
        driverLocationMarker = getTGMap().addMarker(options);
    } else {
        //update position
        driverLocationMarker.setPosition(location);
    }
    getTGMap().animateCamera(CameraUpdateFactory.newLatLng(location));
}

Now let's handle the remaining event type, which is the ETA. We'll parse the date and put it inside of a TextView defined in our custom layout.

private void handleEtaPayload(JSONObject payload) throws JSONException {
    String eta = payload.getString("ETA");
    updateEtaText(eta);
}

private void updateEtaText(String etaText) {
    TextView etaView = findViewById(R.id.tv_eta_text);
    etaView.setText(etaText);
    etaView.setVisibility(View.VISIBLE);
}

And with that, you're done!

You should now be able to run turn-by-turn navigation in the first (driver's) app from the other examples, which will report the current location and ETA to the server whenever it changes, which the server will then broadcast over WebSockets, which the second (this) app will then display on a map. Phew!

Now I can see my burrito travelling towards me in real time, as well as an ETA for when it will get here, which I'm sure you'll agree makes this all worth it.

Check out the Android Reference App to see this example in action.