Build a realtime map using Kotlin
Tags: kotlin, realtime, map, pusher
Published: 2018-05-24 21:44:30

---
title: Build a realtime map using Kotlin
published: true
description: Build a realtime map using Kotlin
tags: kotlin, realtime, map, pusher
---

Just as the name implies, the aim of this article is to show the realtime movement of a marker on a map. This feature is common in location tracking applications. We see taxi apps and food ordering apps making use of features like this. Google provides an extremely easy map API, which we will take advantage of, while the realtime functionalities will be taken care of by Pusher. 

## What we will build

We will build an application that will receive coordinates from the server based on the initial coordinates we inject into it. When these coordinates are received, we update the map on our app.

![](https://thepracticaldev.s3.amazonaws.com/i/0b6ca0rhcl9jwhobc41g.gif)


## Requirements

For this tutorial, we need the following:

* [Android studio](https://developer.android.com/studio/archive.html) - version 3.0.1 or higher is recommended.
* [Node JS](https://nodejs.org/) and NPM installed on your machine.
* A [Pusher](https://dashboard.pusher.com/) application.
* [Google Maps API](https://developers.google.com/maps/documentation/android-api/signup) key.
* An Android device with Google Play Services installed.

## Building our server

We will build our server using Node JS. The server will generate random coordinates for us. To start with, create a new folder. Inside it, create a new file named `package.json` and paste this:

    {
        "main": "index.js",
        "dependencies": {
            "body-parser": "^1.16.0",
            "express": "^4.14.1",
            "pusher": "^1.5.1"
        }
    }


Next, create file called `index.js` in the root directory and paste this:

    // Load the required libraries
    let Pusher = require('pusher');
    let express = require('express');
    let bodyParser = require('body-parser');

    // initialize express and pusher
    let app = express();
    let pusher = new Pusher(require('./config.js'));

    // Middlewares
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));

    // Generates 20 simulated GPS coords and sends to Pusher
    app.post('/simulate', (req, res, next) => {
      let loopCount = 0
      let operator  = 0.001000  
      let longitude = parseFloat(req.body.longitude)
      let latitude  = parseFloat(req.body.latitude)

      let sendToPusher = setInterval(() => {
        loopCount++;

        // Calculate new coordinates and round to 6 decimal places...
        longitude = parseFloat((longitude + operator).toFixed(7))
        latitude  = parseFloat((latitude - operator).toFixed(7))

        // Send to pusher
        pusher.trigger('my-channel', 'new-values', {longitude, latitude})

        if (loopCount === 20) {
          clearInterval(sendToPusher)
        }
      }, 2000);

      res.json({success: 200})
    })

    // Index
    app.get('/', (req, res) => res.json("It works!"));

    // Serve app
    app.listen(4000, _ => console.log('App listening on port 4000!'));

The code above is an Express application. In the `/simulate` route, we are simulating longitude and latitude values and then sending them to Pusher. These will then be picked by our application. 


> 💡 The longitude and latitude values will typically be obtained from the device being tracked in a real-life scenario.

Finally, we will create the configuration file, named `config.js`. Paste this snippet there:

    module.exports = {
        appId: 'PUSHER_APP_ID',
        key: 'PUSHER_APP_KEY',
        secret: 'PUSHER_APP_SECRET',
        cluster: 'PUSHER_APP_CLUSTER',
    };

Replace the values there with the keys from your Pusher dashboard. Then install the modules needed by our server by running this command in the root directory:

    $ npm install

Our server should be up and running on port 4000.

## Building our realtime map in Android Studio

### Create a new Android project

Open Android studio and create a new project. Enter your application details, include Kotlin support, choose a minimum SDK (this should not be less than API 14), choose an Empty Activity, and finish the process. Here is a quick GIF of the process:

![](https://thepracticaldev.s3.amazonaws.com/i/0xkm2eamptyuv9yvvd5p.gif)

### Adding app dependencies

This demo has several dependencies. We need the Pusher dependency for realtime functionality, the Google Maps API for easy integration of maps into our app, and Retrofit to access our server with ease.

Open your app-module `build.gradle` file and paste the following dependencies:

    // Pusher dependency
    implementation 'com.pusher:pusher-java-client:1.5.0'

    // Google maps API
    implementation 'com.google.android.gms:play-services-maps:11.8.0'

    // Retrofit dependencies
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'

Sync your Gradle files so that the libraries can be downloaded and made available.
Building our layout

Open the `activity_main.xml` and paste this:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_marginTop="50dp"
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context="com.example.mapwithmarker.MapsMarkerActivity" />
        <Button
            android:id="@+id/simulateButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Simulate" />

    </FrameLayout>

In the snippet above, we have a fragment which will hold our map and a button.

### Setting up Google Maps API key

It is expected that at this point, you have obtained your API key. You can follow the steps [here](https://developers.google.com/maps/documentation/android-api/signup) to get it. We now want to configure the application with our key. Open your strings.xml file and paste it in. This is located at name-of-project/app/src/main/res/values:

    <resources>
        <!-- ... -->
        <string name="google_maps_key">GOOGLE_MAPS_KEY</string>
    </resources>

> ⚠️ Replace the `GOOGLE_MAPS_KEY` placeholder with the actual key from Google.

This file contains all strings used during the development of the application. All raw strings within the app are kept here. It is required when there is a need to translate your app into multiple languages.

Next, open the `AndroidManifest.xml` file and paste these under the `<application>` tag:

    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="@string/google_maps_key" />

With this, our app knows how and where to fetch our key.


### Setting up Retrofit

We already have Retrofit available as a dependency, but we need two more things - an interface to show endpoints/routes to be accessed and our retrofit object. First create a new Kotlin file name `ApiInterface.kt` and paste this:

    import okhttp3.RequestBody
    import retrofit2.Call
    import retrofit2.http.Body
    import retrofit2.http.POST

    interface ApiInterface {
        @POST("/simulate")
        fun sendCoordinates(@Body coordinates: RequestBody): Call<String>
    }

Since we will make just one request in this demo, we will limit the scope of our Retrofit object to the `MainActivity.kt` class. This means we will create a function within a class for it. Paste this function into the class:

    fun getRetrofitObject(): ApiInterface {
        val httpClient = OkHttpClient.Builder()
        val builder = Retrofit.Builder()
                .baseUrl("http://10.0.3.2:4000/")
                .addConverterFactory(ScalarsConverterFactory.create())

        val retrofit = builder
                .client(httpClient.build())
                .build()
        return retrofit.create(ApiInterface::class.java)
    }

I used a Genymotion emulator and the recognized localhost address for it is 10.0.3.2.

Add the internet permission to the `AndroidManifest.xml` file:

    <uses-permission android:name="android.permission.INTERNET"/>


### Configuring our map and getting realtime updates

For us to initialize and use the map, our the `MainActivity.kt` class must implement the `OnMapReadyCallback` interface and override the `onMapReady` method. We also need to setup Pusher to listen to events and receive the simulated coordinates in realtime. Open your `MainActivity.kt` and paste this:

    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import com.google.android.gms.maps.*
    import com.google.android.gms.maps.model.MarkerOptions
    import com.google.android.gms.maps.model.LatLng
    import com.pusher.client.Pusher
    import com.pusher.client.PusherOptions
    import kotlinx.android.synthetic.main.activity_main.*
    import okhttp3.MediaType
    import okhttp3.OkHttpClient
    import org.json.JSONObject
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response
    import retrofit2.Retrofit
    import retrofit2.converter.scalars.ScalarsConverterFactory
    import okhttp3.RequestBody
    import com.google.android.gms.maps.model.CameraPosition
    import com.google.android.gms.maps.model.Marker

    class MainActivity : AppCompatActivity(), OnMapReadyCallback {
        private lateinit var markerOptions:MarkerOptions
        private lateinit var marker:Marker
        private lateinit var cameraPosition:CameraPosition
        var defaultLongitude = -122.088426
        var defaultLatitude  = 37.388064
        lateinit var googleMap:GoogleMap
        lateinit var pusher:Pusher

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            markerOptions = MarkerOptions()
            val latLng = LatLng(defaultLatitude,defaultLongitude)
            markerOptions.position(latLng)
            cameraPosition = CameraPosition.Builder()
                    .target(latLng)
                    .zoom(17f).build()

        }

        override fun onMapReady(googleMap: GoogleMap?) {
            this.googleMap = googleMap!!
            marker = googleMap.addMarker(markerOptions)
            googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
        }
    }

We first created some class variables to hold our initial coordinates and other map utilities like the camera position and the marker position. We initialized them in the `onCreate` function. Next, we added a click listener to the simulate button.

The next thing to do is still in the `MainActivity.kt` class. In the `onCreate` method, paste this:

    simulateButton.setOnClickListener {
        callServerToSimulate()
    }

When the button is clicked, it calls the `callServerToSimulate` function. Create a function `callServerToSimulate` within the class like this:

    private fun callServerToSimulate() {
        val jsonObject = JSONObject()
        jsonObject.put("latitude",defaultLatitude)
        jsonObject.put("longitude",defaultLongitude)

        val body = RequestBody.create(
            MediaType.parse("application/json"), 
            jsonObject.toString()
        )

        getRetrofitObject().sendCoordinates(body).enqueue(object:Callback<String>{
            override fun onResponse(call: Call<String>?, response: Response<String>?) {
                Log.d("TAG",response!!.body().toString())
            }

            override fun onFailure(call: Call<String>?, t: Throwable?) {
                Log.d("TAG",t!!.message)
            }
        })
    }

In this function, we sent our initial coordinates to our server. The server then generates twenty coordinates similar to the initial ones sent and uses Pusher to send them to channel `my-channel`, firing the `new-values` event.

Next, we create and initialize a `SupportMapFragment` object with the view ID of the map:

    val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
    mapFragment.getMapAsync(this)
    setupPusher()

Next add the the `setupPusher` function to the class and it should looks like this:

    private fun setupPusher() {
        val options = PusherOptions()
        options.setCluster(PUSHER_CLUSTER)
        pusher = Pusher(PUSHER_API_KEY, options)

        val channel = pusher.subscribe("my-channel")

        channel.bind("new-values") { channelName, eventName, data ->
            val jsonObject = JSONObject(data)
            val lat:Double = jsonObject.getString("latitude").toDouble()
            val lon:Double = jsonObject.getString("longitude").toDouble()

            runOnUiThread {
                val newLatLng = LatLng(lat, lon)
                marker.position = newLatLng
                cameraPosition = CameraPosition.Builder()
                        .target(newLatLng)
                        .zoom(17f).build()
                googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
            }
        }
    }

We initialized Pusher here and listened for coordinate updates. When we receive any update, we update our marker and move the camera view towards the new point. You are expected to replace the Pusher parameters with the keys and details found on your Pusher dashboard.

We then call the disconnect and connect functions in the `onPause` and `onResume` functions respectively in the class. These functions are inherited from the parent class `AppCompatActivity`:

    override fun onResume() {
        super.onResume()
        pusher.connect()
    }

    override fun onPause() {
        super.onPause()
        pusher.disconnect()
    }

## Conclusion

We have been able to leverage the power of Pusher, Kotlin and Google Maps API to create a realtime location tracking app. Hopefully you have picked up a thing or two from the tutorial and can use the knowledge to build beautiful realtime apps using Pusher and Kotlin.

This post first appeared on the [Pusher blog](https://pusher.com/tutorials/realtime-map-kotlin).