Real-time Location Tracking with React Native and PubNub

Share this article

Building a Real-time Location Tracking App with React Native and PubNub

With ever-increasing usage of mobile apps, geolocation and tracking functionality can be found in a majority of apps. Real-time geolocation tracking plays an important role in many on-demand services, such as these:

  • taxi services like Uber, Lyft or Ola
  • food Delivery services like Uber Eats, Foodpanda or Zomato
  • monitoring fleets of drones

In this guide, we’re going use React Native to create a real-time location tracking app. We’ll build two React Native apps. One will act as a tracking app (called “Tracking app”) and the other will be the one that’s tracked (“Trackee app”).

Here’s what the final output for this tutorial will look like:

Want to learn React Native from the ground up? This article is an extract from our Premium library. Get an entire collection of React Native books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $9/month.

Prerequisites

This tutorial requires a basic knowledge of React Native. To set up your development machine, follow the official guide here.

Apart from React Native, we’ll also be using PubNub, a third-party service that provides real-time data transfer and updates. We’ll use this service to update the user coordinates in real time.

Register for a free PubNub account here.

Since we’ll be using Google Maps on Android, we’ll also need a Google Maps API key, which you can obtain on the Google Maps Get API key page.

To make sure we’re on the same page, these are the versions used in this tutorial:

  • Node v10.15.0
  • npm 6.4.1
  • yarn 1.16.0
  • react-native 0.59.9
  • react-native-maps 0.24.2
  • pubnub-react 1.2.0

Getting Started

If you want to have a look at the source code of our Tracker and Trackee apps right away, here are their GitHub links:

Let’s start with the Trackee app first.

Trackee App

To create a new project using react-native-cli, type this in the terminal:

$ react-native init trackeeApp
$ cd trackeeApp

Now let’s get to the fun part — the coding.

Add React Native Maps

Since we’ll be using Maps in our app, we’ll need a library for this. We’ll use react-native-maps.

Install react-native-maps by following the installation instructions here.

Add PubNub

Apart from maps, we’ll also install the PubNub React SDK to transfer our data in real time:

$ yarn add pubnub-react

After that, you can now run the app:

$ react-native run-ios
$ react-native run-android

You should see something like this on your simulator/emulator:

Trackee App

Trackee Code

Now, open the App.js file and the following imports:

import React from "react";
import {
  StyleSheet,
  View,
  Platform,
  Dimensions,
  SafeAreaView
} from "react-native";
import MapView, { Marker, AnimatedRegion } from "react-native-maps";
import PubNubReact from "pubnub-react";

Apart from MapView, which will render the Map in our component, we’ve imported Marker and AnimatedRegion from react-native-mas.

Marker identifies a location on a map. We’ll use it to identify user location on the map.

AnimatedRegion allows us to utilize the Animated API to control the map’s center and zoom.

After importing the necessary component, we’ll define some constants and initial values for our Maps:

const { width, height } = Dimensions.get("window");

const ASPECT_RATIO = width / height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

Then, we’ll define our class component with some state, lifecycle methods and custom helper methods:

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      latitude: LATITUDE,
      longitude: LONGITUDE,
      coordinate: new AnimatedRegion({
        latitude: LATITUDE,
        longitude: LONGITUDE,
        latitudeDelta: 0,
        longitudeDelta: 0
      })
    };

    this.pubnub = new PubNubReact({
      publishKey: "X",
      subscribeKey: "X"
    });
    this.pubnub.init(this);
  }

  componentDidMount() {
    this.watchLocation();
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.latitude !== prevState.latitude) {
      this.pubnub.publish({
        message: {
          latitude: this.state.latitude,
          longitude: this.state.longitude
        },
        channel: "location"
      });
    }
  }

  componentWillUnmount() {
    navigator.geolocation.clearWatch(this.watchID);
  }

  watchLocation = () => {
    const { coordinate } = this.state;

    this.watchID = navigator.geolocation.watchPosition(
      position => {
        const { latitude, longitude } = position.coords;

        const newCoordinate = {
          latitude,
          longitude
        };

        if (Platform.OS === "android") {
          if (this.marker) {
            this.marker._component.animateMarkerToCoordinate(
              newCoordinate,
              500 // 500 is the duration to animate the marker
            );
          }
        } else {
          coordinate.timing(newCoordinate).start();
        }

        this.setState({
          latitude,
          longitude
        });
      },
      error => console.log(error),
      {
        enableHighAccuracy: true,
        timeout: 20000,
        maximumAge: 1000,
        distanceFilter: 10
      }
    );
  };

  getMapRegion = () => ({
    latitude: this.state.latitude,
    longitude: this.state.longitude,
    latitudeDelta: LATITUDE_DELTA,
    longitudeDelta: LONGITUDE_DELTA
  });

  render() {
    return (
      <SafeAreaView style={{ flex: 1 }}>
        <View style={styles.container}>
          <MapView
            style={styles.map}
            showUserLocation
            followUserLocation
            loadingEnabled
            region={this.getMapRegion()}
          >
            <Marker.Animated
              ref={marker => {
                this.marker = marker;
              }}
              coordinate={this.state.coordinate}
            />
          </MapView>
        </View>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: "flex-end",
    alignItems: "center"
  },
  map: {
    ...StyleSheet.absoluteFillObject
  }
});

Whew! That’s a lot of code, so let’s walk through it bit by bit.

First, we’ve initialized some local state in our constructor(). We’ll also initialize a PubNub instance:

constructor(props) {
  super(props);

  this.state = {
    latitude: LATITUDE,
    longitude: LONGITUDE,
    coordinate: new AnimatedRegion({
      latitude: LATITUDE,
      longitude: LONGITUDE,
      latitudeDelta: 0,
      longitudeDelta: 0,
    }),
  };

  // Initialize PubNub
  this.pubnub = new PubNubReact({
    publishKey: 'X',
    subscribeKey: 'X',
  });

  this.pubnub.init(this);
}

You’ll need to replace “X” with your own PubNub publish and subscribe keys. To get your keys, log in to your PubNub account and go to the dashboard.

You’ll find a Demo Project app already available there. You’re free to create a new app, but for this tutorial we’ll use this Demo project.

Copy and Paste the keys in the PubNub constructor instance.

After that, we’ll use the componentDidMount() Lifecycle to call the watchLocation method:

componentDidMount() {
  this.watchLocation();
}

watchLocation = () => {
  const { coordinate } = this.state;

  this.watchID = navigator.geolocation.watchPosition(
    position => {
      const { latitude, longitude } = position.coords;

      const newCoordinate = {
        latitude,
        longitude,
      };

      if (Platform.OS === 'android') {
        if (this.marker) {
          this.marker._component.animateMarkerToCoordinate(newCoordinate, 500); // 500 is the duration to animate the marker
        }
      } else {
        coordinate.timing(newCoordinate).start();
      }

      this.setState({
        latitude,
        longitude,
      });
    },
    error => console.log(error),
    {
      enableHighAccuracy: true,
      timeout: 20000,
      maximumAge: 1000,
      distanceFilter: 10,
    }
  );
};

The watchLocation uses the geolocation API to watch changes in user’s location coordinates. So any time the user moves and his position coordinates changes, watchPosition will return the user’s new coordinates.

The watchPosition accepts two parameters—options and callback.

As options, we’ll set enableHighAccuracy to true for high accuracy, and distanceInterval to 10 to receive updates only when the location has changed by at least ten meters in distance. If you want maximum accuracy, use 0, but be aware that it will use more bandwidth and data.

In the callback, we get the position coordinates and we call use these coordinates to set the local state variables.

const { latitude, longitude } = position.coords;

this.setState({
  latitude,
  longitude
});

Now that we have the user coordinates, we’ll use them to add a marker on the map and then update that marker continuously as the user coordinates changes with its position.

For this, we’ll use animateMarkerToCoordinate() for Android and coordinate.timing() for iOS. We’ll pass an object newCoordinate with latitude and longitude as a parameter to these methods:

if (Platform.OS === "android") {
  if (this.marker) {
    this.marker._component.animateMarkerToCoordinate(newCoordinate, 500); // 500 is the duration to animate the marker
  }
} else {
  coordinate.timing(newCoordinate).start();
}

We also want the user’s coordinates to be sent continuously to our Tracker app. To achieve this, we’ll use React’s componentDidUpdate lifecycle method:

 componentDidUpdate(prevProps, prevState) {
  if (this.props.latitude !== prevState.latitude) {
    this.pubnub.publish({
      message: {
        latitude: this.state.latitude,
        longitude: this.state.longitude,
      },
      channel: 'location',
    });
  }
}

The componentDidUpdate is invoked immediately after the updating occurs. So it will be called each time the user’s coordinates get changed.

We’ve further use an if condition to publish the coordinates only when the latitude is changed.

We then called the PubNub publish method to publish the coordinates, along with the channel name location we want to publish those coordinates.

Note: make sure the channel name is the same in both the apps. Otherwise, you won’t receive any data.

Now that we’re done with all the required methods, let’s render our MapView. Add this code in your render method:

return (
  <SafeAreaView style={{ flex: 1 }}>
    <View style={styles.container}>
      <MapView
        style={styles.map}
        showUserLocation
        followUserLocation
        loadingEnabled
        region={this.getMapRegion()}
      >
        <Marker.Animated
          ref={marker => {
            this.marker = marker;
          }}
          coordinate={this.state.coordinate}
        />
      </MapView>
    </View>
  </SafeAreaView>
);

We’ve used Marker.Animated, which will move in an animated manner as users moves and their coordinates change.

componentWillUnmount() {
  navigator.geolocation.clearWatch(this.watchID);
}

We’ll also clear all geolocation watch method in componentWillUnmount() to avoid any memory leaks.

Let’s finish the Trackee app by adding some styles:

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: "flex-end",
    alignItems: "center"
  },
  map: {
    ...StyleSheet.absoluteFillObject
  }
});

Since we want our map to cover the whole screen, we have to use absolute positioning and set each side to zero (position: 'absolute', left: 0, right: 0, top: 0, bottom: 0).

StyleSheet provides absoluteFill that can be used for convenience and to reduce duplication of these repeated styles.

Running the Trackee App

Before we go any further, it’s always a good idea to test our app. We can do so by taking the following steps.

On iOS

If you’re using iOS simulator, you’re in luck. It’s very easy to test this feature in iOS compared to Android.

In your iOS simulator settings, go to Debug > Location > Freeway Drive and refresh your app (Cmd + R). You should see something like this:

Trackee App

On Android

Unfortunately for Android, there’s no straightforward way of testing this feature.

You can use third-party apps to imitate GPS location apps. I found GPS Joystick to be of great help.

You can also use Genymotion, which has a utility for simulating the location.

Testing on PubNub

To test if PubNub is receiving data, you can turn on Real-time Analytics, which will show the number of messages your app is receiving or sending.

In your Keys tab, go to the bottom and turn on Real-time Analytics. Then go to Real-time Analytics to the check if the data is being received.

This is all the Trackee app needs to do, so let’s move on to the Tracker app.

Tracker App

Follow the same steps as we did for Trackee app and create a new React Native project called trackerApp.

Both Tracker and Trackee apps share the majority their code.

The only difference is that in trackerApp we’ll be getting the location coordinates from the trackeeApp via PubNub.

Add the pubnub-react SDK, import and initialize as we did in the Trackee app.

In componentDidMount(), add the following:

// same imports as trackeeApp

componentDidMount() {
  /* remove
    watchLocation = () => {}
  */

 // add:
  this.subscribeToPubNub();
}

// add:
subscribeToPubNub = () => {
  this.pubnub.subscribe({
    channels: ['location'],
    withPresence: true,
  });
  this.pubnub.getMessage('location', msg => {
    const { coordinate } = this.state;
    const { latitude, longitude } = msg.message;
    const newCoordinate = { latitude, longitude };

    if (Platform.OS === 'android') {
      if (this.marker) {
        this.marker._component.animateMarkerToCoordinate(newCoordinate, 500);
      }
    } else {
      coordinate.timing(newCoordinate).start();
    }

    this.setState({
      latitude,
      longitude,
    });
  });
};

/* remove
watchLocation = () => {
}
*/

Here is the sneak peak of the updated code for the Tracker app.

In the code above, we’re using PubNub’s subscribe method to subscribe to our location channel as soon the component gets mounted.

After that, we’re using getMessage to get the messages received on that channel.

We’ll use these coordinates to update the MapView of the Tracker app.

Since both the apps share the same set of coordinates, we should be able to see the Trackee app coordinates in the Tracker app.

Running Both Apps Together

Finally we’re at the last step. It’s not straightforward to test both apps on the same machine under development mode.

To test both the apps on an iOS machine, I’m going to follow these steps:

  1. We’re going to run the Trackee app on the iOS simulator since, it has the debug mode where I can simulate a moving vehicle. I’m also going to run it on release mode, as we can’t have two packages running at the same time:

     $ react-native run-ios --configuration Release
    

    Now, go to Debug > Location > Freeway Drive.

  2. We’ll run the Tracker app on Android emulator:

     $ react-native run-android
    

The Tracker app now should be able to se the Marker moving just like in the Trackee app.

You can find the source code for both apps on GitHub.

Conclusion

This is just a very basic implementation of Real-time Location Tracking services. We’re just scratching the surface with what we can achieved with location tracking. In reality, the possibilities are endless. For example:

  • You could create a ride-hailing service like Uber, Lyft etc.
  • Using Location tracking, you could track your orders like food or grocery from the local seller.
  • You could track the location of your children (useful for parents or teachers).
  • You could track animals in a protected national park.

If you use this to create your own implementation of location tracking, I’d love to see the results. Let me know on Twitter.

Frequently Asked Questions (FAQs) on React Native Real-Time Location Tracking

How Can I Implement Real-Time Location Tracking in React Native?

Real-time location tracking in React Native can be implemented using the PubNub React SDK. First, you need to install the PubNub React SDK using npm or yarn. Then, you can import the PubNub React SDK into your project. After that, you can initialize PubNub by providing your publish and subscribe keys. You can then use the publish method to send location updates and the subscribe method to receive location updates. You can also use the hereNow method to get the current location of all devices.

How Can I Handle Location Permissions in React Native?

Handling location permissions in React Native can be done using the PermissionsAndroid API. You can use the request method to request a permission and the check method to check if a permission is granted. You should always request the location permission before trying to access the device’s location.

How Can I Track the Location in the Background in React Native?

Tracking the location in the background in React Native can be done using the react-native-background-geolocation library. This library provides a comprehensive set of features for background location tracking, including automatic tracking, geofencing, and activity recognition. You can install this library using npm or yarn and then import it into your project.

How Can I Display the Tracked Location on a Map in React Native?

Displaying the tracked location on a map in React Native can be done using the react-native-maps library. This library provides a MapView component that can display a map and Marker components that can display markers on the map. You can update the markers’ positions based on the received location updates.

How Can I Optimize the Battery Usage of Location Tracking in React Native?

Optimizing the battery usage of location tracking in React Native can be done by adjusting the location update interval and the location accuracy. You can set a longer update interval and a lower accuracy when the app is in the background to reduce the battery usage. You can also use the react-native-background-geolocation library, which provides automatic battery management.

How Can I Handle Location Tracking Errors in React Native?

Handling location tracking errors in React Native can be done by using error callbacks and checking the error codes. You should always handle possible errors, such as the location permission being denied or the location services being disabled.

How Can I Test Location Tracking in React Native?

Testing location tracking in React Native can be done by using the emulator’s location settings or a real device. You can set a custom location in the emulator or move around with a real device to test the location tracking.

How Can I Implement Geofencing in React Native?

Implementing geofencing in React Native can be done using the react-native-background-geolocation library. This library provides a Geofence class that you can use to define geofences and methods to add and remove geofences. You can also use the onGeofence event to handle geofence events.

How Can I Share the Tracked Location with Other Users in React Native?

Sharing the tracked location with other users in React Native can be done using the PubNub React SDK. You can use the publish method to send location updates to a channel and the subscribe method to receive location updates from a channel. All users subscribed to the same channel will receive the same location updates.

How Can I Secure the Location Data in React Native?

Securing the location data in React Native can be done by using encryption and secure channels. You can use the crypto library to encrypt the location data before sending it and decrypt it after receiving it. You can also use secure channels in PubNub to ensure that the location data is securely transmitted.

Vikrant NegiVikrant Negi
View Author

Web developer | Open Source | Pseudo Nerd | esports | vikrantnegi.com

location trackingPubNubReact native
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week