Real-time Location Tracking 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 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:
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:
-
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.
-
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.