Creating a Real-Time Location Tracking App with NativeScript-Vue
Tags: vue, nativescript, javascript, api
Published: 2020-03-23 15:11:46

---
title: Creating a Real-Time Location Tracking App with NativeScript-Vue
published: true
description: In this blog post, we explore the NativeScript-Vue template for building a real-time location tracking app that uses the Google Maps API.
tags: vue, nativescript, javascript, api
canonical_url: https://blog.jscrambler.com/creating-a-real-time-location-tracking-app-with-nativescript-vue/
cover_image: https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue.jpg
---

In this tutorial, you're going to learn how to create a real-time location tracking app using the NativeScript Vue template.

## Prerequisites

Basic knowledge of NativeScript is required to follow this tutorial. Vue knowledge is optional.

The following package versions were used in creating this tutorial:

- Node 12.7.0
- Yarn 1.19.1
- Nativescript-Vue 2.4.0
- NativeScript CLI 6.1.2

Be sure to install the same versions or higher to ensure that the app will work.

Lastly, you need to have a [Pusher](https://pusher.com/) and [Google account](https://myaccount.google.com/) so you can use their API.

## App Overview

We're going to create a real-time location tracking app. It will allow the user to either share their current location or track another person's location via a map interface. Here's what it will look like:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-1](https://i.imgur.com/hPsO0c6.jpg)

You can view the source code on this GitHub [repo](https://github.com/JscramblerBlog/NativeScript-Realtime-Location).

## Setting up a Pusher app instance

We need a Pusher app instance in order to use Pusher's services. Go to the [Pusher dashboard](https://dashboard.pusher.com/) and create a new Channels app:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-2](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-2.png)

Once the app is created, go to the **app settings** tab and enable client events. This is what will allow the users to trigger real-time events directly from the client-side:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-3](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-3.png)

## Setting up Google Maps API

To use Google Maps, we need to enable the Google Maps SDK on the Google Cloud Platform console.

On your dashboard, click on the burger menu on the upper left side, hover over **APIs & Services** and click on **Library**:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-4](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-4.png)

Once you're in the libraries page, search for "Maps SDK", click on **Maps SDK for Android** and enable it:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-5](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-5.png)

Next, go to **API & Services** -> **Credentials** and click on the **Create Credentials** button. Then, select **API key** on the dropdown that shows up:

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-6](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-6.png)

That will generate a new API key that you can use later on in the app. Note that you should also restrict access to that key so that it can only be used in the app.

## Setting up the project

The app will have both a server and app component. We'll start by setting up the app itself.

### Setting up the app

Create a new NativeScript project that uses the Vue template:

```bash
tns create LocationTracker --vue
```

Once that's done, navigate to the newly generated `LocationTracker` directory and install the dependencies as below:

```bash
tns plugin add nativescript-geolocation
tns plugin add nativescript-google-maps-sdk
tns plugin add nativescript-permissions
tns plugin add nativescript-websockets
tns plugin add pusher-nativescript
```

Next, we need to install the library for generating random strings:

```bash
yarn add random-string
```

Here's a brief overview of the packages we just installed:

-   [**nativescript-geolocation**](https://github.com/NativeScript/nativescript-geolocation) — used for getting the user's current location.
-   [**nativescript-google-maps-sdk**](https://github.com/dapriett/nativescript-google-maps-sdk) — NativeScript library for working with the Google Maps SDK.
-   [**nativescript-permissions**](https://github.com/NathanaelA/nativescript-permissions) — used for asking permissions in Android.
-   [**nativescript-websockets**](https://github.com/NathanaelA/nativescript-websockets) — WebSocket library for NativeScript. Pusher uses WebSockets so this is a dependency for **pusher-nativescript**.
-   [**pusher-nativescript**](https://github.com/slidebean/pusher-nativescript) — NativeScript library for Pusher integration.
-   [**random-string**](https://github.com/valiton/node-random-string) — for generating random strings that will serve as the unique ID for users who want to share their location.

Once everything is installed, update the `app/App_Resources/Android/src/main/AndroidManifest.xml` file. Add the following permissions so we can access the user's current location:

```xml
<manifest>
    <<!-- ... -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- add these-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>
```

Then, under `<application>`, add the `<meta-data>` for the Google API key:

```xml
<application>
    ...
    <meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_GOOGLE_API_KEY" />
</application>
```

### Setting up the server

For the server, create a `server` folder inside your working directory and create a `package.json` file with the following contents:

```json
{
  "name": "ns-realtime-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "pusher": "^3.0.0"
  }
}
```

Once that's done, execute `yarn` inside the `server` folder to install all the packages.

Here's a brief overview of the packages we just installed:

- [**express**](https://github.com/expressjs/express) — for creating a server.
- [**dotenv**](https://github.com/motdotla/dotenv) — allows fetching environment variables (app config) in a `.env` file.
- [**cors**](https://github.com/expressjs/cors) — allows the app to make requests to the server.
- [**body-parser**](https://github.com/expressjs/body-parser) — for parsing the request body to a JavaScript object.
- [**pusher**](https://github.com/pusher/pusher-http-node) — for real-time communications.

## Building the app

Now, we're ready to build the app. We'll start by adding the server code, then we'll proceed to adding the code for the app itself.

### Adding the server code

Create an `index.js` file and add the following. This will import all the packages that we need:

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
require("dotenv").config();
const Pusher = require("pusher");
```

Next, initialize Pusher. This is what will allow us to connect to the Pusher app instance we created earlier:

```javascript
const pusher = new Pusher({
    appId: process.env.PUSHER_APP_ID,
    key: process.env.PUSHER_APP_KEY,
    secret: process.env.PUSHER_APP_SECRET,
    cluster: process.env.PUSHER_APP_CLUSTER
});
```

Next, initialize the Express server. Here, we need to enable CORS (Cross-origin resource sharing) so that the app can make a request to the server:

```javascript
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false })); // disables nested object in the request body
```

For the server, we'll only need a couple of routes: one for testing if the server is running and the other for authenticating users of the app so that they can trigger messages directly from the client-side:

```javascript
app.get("/", (req, res) => {
    res.send("ok");
});

app.post("/pusher/auth", (req, res) => {
    const socketId = req.body.socket_id;
    const channel = req.body.channel_name;
    const auth = pusher.authenticate(socketId, channel);
    res.send(auth);
});
```

> The authentication code above simply authenticates every request that comes in. In a production app, you would need to check in your database if the user who made the request exists.

Lastly, expose the server:

```javascript
const PORT = 5000;
app.listen(PORT, err => {
    if (err) {
        console.error(err);
    } else {
        console.log(`Running on ports ${PORT}`);
    }
});
```

### Adding the app code

Now, we're ready to add the code for the app. Start by opening the `app/app.js` file and register the `MapView` component. We need to do this because, by default, the Google Maps library for NativeScript doesn't support Vue. The code below is used to manually register the `MapView` element so we can use it in our templates:

```javascript
// app/app.js
Vue.registerElement(
    "MapView",
    () => require("nativescript-google-maps-sdk").MapView
);
```

<a href="https://blog.jscrambler.com/how-to-protect-your-vue-js-application-with-jscrambler/?utm_source=dev.to&utm_medium=referral&utm_campaign=real-time-ns-vue" target="_blank"><img alt="Protect your Vue App with Jscrambler" src="https://media.jscrambler.com/blog/V1-Vue.jpg"></a>

Next, open the `app/components/Home.vue` file, clear its contents and add the following template. This is going to render the map inside a `StackLayout`. Its height is set to 85% so that there will be space for the buttons that the user will use to either share or track location. The `MapView` uses the `latitude`, `longitude`, and `zoom` values that we will set later as the data for this component:

```xml
<template>
    <Page actionBarHidden="true" backgroundSpanUnderStatusBar="false">

        <StackLayout height="100%" width="100%" >
            <MapView
                :latitude="latitude"
                :longitude="longitude"
                :zoom="zoom"
                height="85%"
                @mapReady="onMapReady">
            </MapView>
            <Button text="Stop Sharing Location" @tap="stopSharingLocation" v-if="isSharingLocation"></Button>
            <Button text="Share Location" @tap="startSharingLocation" v-else="isSharingLocation"></Button>

            <Button text="Stop Tracking Location" @tap="stopTrackingLocation" v-if="isTrackingLocation"></Button>
            <Button text="Track Location" @tap="startTrackingLocation" v-else="isTrackingLocation"></Button>
        </StackLayout>
    </Page>
</template>
```

Right below the component UI, we add the JavaScript code. Start by importing the packages we need:

```javascript
import * as geolocation from "nativescript-geolocation";
import * as dialogs from "tns-core-modules/ui/dialogs";
import { Position, Marker } from "nativescript-google-maps-sdk";
import { Accuracy } from "tns-core-modules/ui/enums";
import Pusher from "pusher-nativescript";
const randomString = require("random-string");
```

Next, add the Pusher app config. Leave the `SERVER_BASE_URL` for now — that will have to be an internet-accessible URL. So, we'll use [ngrok](https://ngrok.com/) to expose the local server:

```javascript
const PUSHER_APP_KEY = "YOUR PUSHER APP KEY";
const PUSHER_APP_CLUSTER = "YOUR PUSHER APP CLUSTER";
const SERVER_BASE_URL = "YOUR PUSHER AUTH SERVER URL";
```

Next, initialize the data to bind to the component:

```javascript
export default {
    data() {
        return {
            // current coordinates being displayed on the map
            latitude: "",
            longitude: "",

            zoom: 17, // map zoom level

            mapView: null, // map view being rendered

            marker: new Marker(), // google map marker
            watchID: null, // unique ID for the watch location instance

            isSharingLocation: false, // whether the current user is sharing their location or not
            isTrackingLocation: false, // whether the current user is tracking someone else's location or not

            ownID: null, // unique ID of the current user for tracking
            trackingID: null, // unique ID of the person being tracked
            socket: null, // pusher socket

            ownChannel: null, // current user's own channel for triggering events
            trackingChannel: null // channel of the user being tracked by the current user
        };
    }

    // next: add mounted()
};
```



After this, add the methods to be bind to the component. First is `onMapReady()`, which we've attached to the `mapReady` event of `MapView`. This gets called once the `MapView` component is ready for use. `args.object` represents the map itself. Assigning it to data bound to the component allows us to manipulate the map later on:

```javascript
methods: {
    onMapReady(args) {
        this.mapView = args.object;
    }
}
```

Next, add the `mounted()` method. This gets fired when the component is mounted. This is where we generate a unique ID for location sharing. Once that's done, we check if Geolocation (location services) is enabled. If it isn’t, then we request it from the user by calling `geolocation.enableLocationRequest()`. If the user enabled it, we proceed to getting their current location and updating the map:

```javascript
methods: {
    onMapReady() {
        // ...
    }
},

mounted() {
    this.ownID = randomString({length: 5}); // unique ID for sharing location
    let that = this

    geolocation.isEnabled().then(function(isEnabled) {
        if (!isEnabled) { // GPS is not enabled
            geolocation.enableLocationRequest(true, true).then(() => {

                geolocation
                    .getCurrentLocation({
                        timeout: 20000
                    })
                    .then(location => {
                        if (!location) {
                            dialogs.alert('Failed to get location. Please restart the app.');
                        } else {
                            // show the user's current location in the map and add the marker
                            that.updateMap(location);
                            that.mapView.addMarker(that.marker);
                        }
                    });
            }, (e) => {
                console.log("error: " + (e.message || e));
            }).catch(ex => {
                console.log("Unable to Enable Location", ex);
            });
        } else {
            // GPS is enabled
            geolocation
                .getCurrentLocation({
                    timeout: 20000
                })
                .then(location => {
                    if (!location) {
                        dialogs.alert('Failed to get location. Please restart the app.');
                    } else {
                        that.updateMap(location);
                        that.mapView.addMarker(that.marker);
                    }
                });
        }
    }, function(e) {
        console.log("error: " + (e.message || e));
    });

    // next: subscribe to own Pusher channel

},
```

Once that’s done, initialize Pusher and subscribe to the user's own channel. This is where we use the unique ID we generated earlier to subscribe to a [private channel](https://pusher.com/docs/channels/using_channels/private-channels). We're using a private channel because we only want authenticated users to use the channel:

```javascript
this.socket = new Pusher(PUSHER_APP_KEY, {
    cluster: PUSHER_APP_CLUSTER,
    authEndpoint: `${SERVER_BASE_URL}/pusher/auth`
});

this.ownChannel = this.socket.subscribe(`private-${this.ownID}`);

this.ownChannel.bind("pusher:subscription_error", () => {
    dialogs.alert("Failed to connect. Please restart the app.");
});
```

Below, we have the `updateMap()` function. This sets the map coordinates to the location passed as an argument. After that, it also changes the marker position:

```javascript
updateMap(loc) {
    this.latitude = loc.latitude;
    this.longitude = loc.longitude;
    this.marker.position = Position.positionFromLatLng(
        loc.latitude,
        loc.longitude
    );
},
```

Next, add the `startSharingLocation()` method. This will show the user their unique ID so that they could share it with someone. After that, the app will begin to watch the user's current location via the `geolocation.watchLocation()` method. This accepts the success callback as the first argument and the error callback as the second. The third argument is the options.

In this case, we're setting the `updateDistance` to `5` meters so that it will only fire the success callback if the change in the distance traveled is 5 meters or more. On the other hand, `minimumUpdateTime` is the minimum time interval between each location update. `desiredAccuracy` relates to the level of accuracy of the coordinates. `Accuracy.high` is the finest location available, so it consumes more battery. When the success callback is fired, it's going to update the map and trigger the `client-location-changed` event. The current location is passed to this so whoever subscribes to that same event will get updates in real-time:

```javascript
methods: {
    onMapReady() {
        // ..
    },

    startSharingLocation() {
        dialogs.alert(`Your unique ID is: ${this.ownID}`);
        this.isSharingLocation = true;

        this.watchID = geolocation.watchLocation(
            (loc) => {
                if (loc) {
                    this.updateMap(loc);
                    this.ownChannel.trigger('client-location-changed', {
                        latitude: loc.latitude,
                        longitude: loc.longitude
                    });
                }
            },
            (e) => {
                dialogs.alert(e.message);
            },
            {
                updateDistance: 5, // 5 meters
                minimumUpdateTime : 5000, // update every 5 seconds
                desiredAccuracy: Accuracy.high,
            }
        );
    },

    // next: add stopSharingLocation()
}
```

Next, add the code for stopping the location sharing. This is where we use `this.watchID` to stop watching the location:

```javascript
stopSharingLocation() {
    this.isSharingLocation = false;
    geolocation.clearWatch(this.watchID);
},
```

For users who want to track the location of another user, we ask them to enter the unique ID. From there, we simply subscribe to the channel with that ID and bind to `client-location-changed` to receive real-time updates:

```javascript
startTrackingLocation() {
    dialogs.prompt("Enter unique ID", "").then((r) => {

        this.trackingID = r.text;
        this.isTrackingLocation = true;
        this.trackingChannel = this.socket.subscribe(`private-${this.trackingID}`);
        this.trackingChannel.bind('pusher:subscription_succeeded', () => {

            this.trackingChannel.bind('client-location-changed', (loc) => {
                this.updateMap(loc);
            });
        });
    });
},
```

Lastly, add the code for stopping the location tracking:

```javascript
stopTrackingLocation() {
    this.socket.unsubscribe(`private-${this.trackingID}`);
    this.isTrackingLocation = false;
},
```

## Running the app

At this point, we're ready to run the app. First, start the server:

```bash
node server/index.js
```

Next, expose the server using ngrok:

```bash
./ngrok http 5000
```

Then, update the `app/components/Home.vue` file with the ngrok URL:

```javascript
const SERVER_BASE_URL = 'YOUR NGROK HTTPS URL';
```

You can run the app either on the emulator or a real device:

```bash
tns debug android
```

However, since the nature of the app requires us to change locations, it's easier if you use an emulator for testing. This way, you can easily change the location by searching for a specific location or pointing to a specific location via a map interface. The [Genymotion](https://www.genymotion.com/fun-zone/) emulator allows you to do it very easily.

![jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-7](https://blog.jscrambler.com/content/images/2020/03/jscrambler-blog-creating-real-time-tracking-app-nativescript-vue-7.png)

Alternatively, you can also use a fake location app such as [Floater](https://play.google.com/store/apps/details?id=com.marlon.floating.fake.location) on your Android device. This will allow you to spoof the current location and specify a different one via a map interface — though I've had an issue with this method. It seems that it switches the current location and the fake location back and forth, which beats the whole purpose because you can't properly test the functionality.

## Conclusion

That's it! In this tutorial, you learned how to create a real-time location tracking app in NativeScript.

Along the way, you learned how to work with the NativeScript Vue template, rendering Google Maps, watching the user's current location, and publishing it in real-time.

---

As always, we recommend that you protect your JavaScript source code when you're developing commercial or enterprise apps. See our tutorials on protecting <a href="https://blog.jscrambler.com/how-to-protect-your-vue-js-application-with-jscrambler/?utm_source=dev.to&utm_medium=referral&utm_campaign=real-time-ns-vue">Vue</a> and <a href="https://blog.jscrambler.com/protecting-your-nativescript-source-code-with-jscrambler/?utm_source=dev.to&utm_medium=referral&utm_campaign=real-time-ns-vue">NativeScript</a>.