At PubNub, we believe in powering the global frontier of remote interactions. Whether it be the burgeoning paradigm of IoT, the global growth of chat apps, or the relentless expansion of online applications, our mission extends to Internet technologies of all shapes and sizes. Using PubNub's Geolocation APIs to track your fleet’s cars, check on your drivers’ deliveries, provision orders, or even matchmake in dating apps. Whether you want to build a rideshare app, on-demand delivery service, or a Pokemon Go game, you can depend on PubNub's scalability and reliability.
In this tutorial, you'll learn how easy it is to build real-time geolocation tracking with PubNub and React Native, one of the most popular mobile app frameworks. The geolocation application renders multiple users on a map view, allowing users to toggle their location permissions as well as click a button to zoom the map on their location. You'll also be able to determine how many users are currently online in the app using Presence.
Although this tutorial guides you step-by-step to create the geolocation React Native app, you can find the final source code of the application in the GitHub repository and read our article to learn more about geolocation APIs.
In this section, you will install the necessary tools and dependencies to be able to simulate, run, and test the React Native application.
You'll need to create a free PubNub account, as you'll need your publish/subscribe keys to send information across the PubNub Network. You can learn how to create your keys in this video/written walkthrough.
Ensure that you enable Presence to know when users come online and Message Persistence to persist information.
You will need to download and use your favorite code editor when developing this application. Visual Studio Code is recommended as it is a lightweight, open-source, and free code editor that supports different languages and frameworks. You should also add the React Native Tools plugin for debugging and integrated commands for React Native.
The next set of tools you’ll need for developing cross-platform applications is to download iOS and Android development environments. For iOS devices, you'll need to download Xcode. For Mac users, you can simply download Xcode for free in the app store. For PC users, you will need to simulate Mac OS with a Virtual Machine if you want to develop your app for iPhone. You can see how to do this here. For simulating Android applications, you'll need to download Android Studio.
Download Node.js, the JavaScript runtime environment, as it is necessary to run React Native applications, as well as install packages with npm
(which is included with the download).
Next, you'll install React Native. This is an open-source platform developed by Facebook that has become very popular over the years. React Native allows developers to write their applications in one language across multiple platforms, which allows you to develop for Android and iOS devices.
To set up the React Native development environment, you'll be using the React Native CLI, which will allow you to quickly install libraries, link packages, and simulate the app.
Navigate in the terminal/console to the folder you would like to develop this application. Use npm to install the React Native CLI command line utility.
sudo npm install -g react-native-cli sudo npm install -g react-native
Note: You will be prompted to install CocoaPods after this installation. Say yes to this download and make sure you don’t run CocoaPods as root (i.e. make sure you installed react-native first using sudo
and then you can use react-native as a normal user. CocoaPods then should run properly).
Then run the following commands to create a new React Native project called YourProject.
react-native init YourProject cd YourProject
To see if everything’s working properly, run this command to test your app with Xcode’s iPhone simulator.
sudo react-native run-ios
If you’re having issues with the react-native run-ios
command, try building the project in Xcode. Make sure to specify iPhone X as the simulator.
Although you will need to add some additional logic for the application to be compatible with Android devices, you can run the project in the Android emulator with the following command.
react-native run-android
Once you've ensured that everything is properly working, you are going to import and link PubNub’s React SDK to be able to communicate across the PubNub network. In your project directory, install the PubNub React SDK and link the library using the following commands.
npm install --save pubnub pubnub-react@1 react-native link pubnub-react
Next, you'll need the GitHub react-native-maps library made by Airbnb for the interactive map API. Install and link the library.
npm install react-native-maps --save react-native link react-native-maps
Finally, install and link the react-native-responsive package, which will make styling components easier.
npm install --save react-native-responsive-screen react-native link react-native-responsive-screen
Now it’s time to finally start building the geolocation application with real-time updates.
In the project directory, open up the App.js file and import the libraries that were installed earlier along with some basic React Native components using the following code.
import React, {Component} from 'react'; import {Platform, StyleSheet, Text, View, TouchableOpacity, Switch, Image} from 'react-native'; import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen'; import MapView, {Marker} from 'react-native-maps'; import PubNubReact from 'pubnub-react';
In the same App.js file, initialize a constructor to pass in the props
as well as initialize a PubNub instance.
constructor(props) { super(props); this.pubnub = new PubNubReact({ publishKey: "YOUR PUBLISH KEY", subscribeKey: "YOUR SUBSCRIBE KEY" }); this.pubnub.init(this); }
A PubNub instance is created with the Publish and Subscribe API keys from your PubNub account and then initialized at the end of the constructor. It is very important that the PubNub instance is initialized at the end of the constructor as the code will not work if done otherwise.
Next, create some state variables that you will need while the app is running. If you haven’t used state in React yet, it may be useful to read up about state in React Native before moving on.
constructor(props) { super(props); this.pubnub = new PubNubReact({ publishKey: "YOUR PUBLISH KEY", subscribeKey: "YOUR SUBSCRIBE KEY" }); //Base State this.state = { currentLoc: { //Track user's current location latitude: -1, longitude: -1 }, numUsers: 0, //track number of users on the app username: "A Naughty Moose", //user's username fixedOnUUID: "", focusOnMe: false, //zoom map to user's current location if true users: new Map(), //store data of each user in a Map isFocused: false, allowGPS: true, //toggle the app's ability to gather GPS data of the user }; this.pubnub.init(this); }
While most of the state variables seem fairly intuitive for a Geotracking app, the users
map requires further explanation.
The users
map facilitates how multiple users are rendered in the app. Each entry in the map will represent one user and will map to the specific in-app data the user contains (GPS coordinates, UUID, allowGPS
, etc). PubNub is then used to publish JSON data updates from each user to update the mapping and re-render the application’s state variables accordingly.
For example, if you want to update a user’s allowGPS
property, you publish a JSON object to update that user’s variable mapping.
this.pubnub.publish({ message: { hideUser: true }, channel: "channel" });
Once the state variables are initialized, you'll declare an asynchronous function in ComponentDidMount
.
async componentDidMount() { this.setUpApp() }
In order to start receiving PubNub messages within the app, you need to declare a PubNub event listener followed by a PubNub subscriber callback, specifying the channel.
async setUpApp(){ this.pubnub.getMessage("YOUR CHANNEL", msg => { /*------------WE'LL IMPLEMENT THIS LATER------------*/ }); this.pubnub.subscribe({ channels: ["YOUR CHANNEL"], }); }
This function will be fully implemented later in the tutorial.
You will now start implementing the interactive map for your users as well as track their GPS data.
To collect a user’s position, implement the react-native-maps watchPosition
function below the PubNub subscriber.
//Track motion Coordinates navigator.geolocation.watchPosition( position => { this.setState({ currentLoc: position.coords }); if (this.state.allowGPS) { this.pubnub.publish({ message: { latitude: position.coords.latitude, longitude: position.coords.longitude, }, channel: "channel" }); } //console.log(positon.coords); }, error => console.log("Maps Error: ", error), { enableHighAccuracy: true, distanceFilter: 100 //grab the location whenever the user's location changes by 100 meters } );
You should now be able to see the logic for the user
map framework. After collecting the geohash position coordinates, the latitude and longitude data are published to the channel. The channel will later update this user’s location data based on the publisher’s UUID. Just like any other map application, a function is added to center the map on the user’s location if a button is pressed. To add this functionality, add this function implementation.
focusLoc = () => { if (this.state.focusOnMe || this.state.fixedOnUUID) { this.setState({ focusOnMe: false, fixedOnUUID: "" }); } else { region = { latitude: this.state.currentLoc.latitude, longitude: this.state.currentLoc.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 }; this.setState({ focusOnMe: true }); this.map.animateToRegion(region, 2000); } }
When called, this function will center the map’s viewing region on the user’s current location. If you want the user to have the ability to turn off their GPS location, the allowGPS
state needs to be toggled. To do this, add the following function.
toggleGPS = () => { this.setState({ allowGPS: !this.state.allowGPS }); };
Navigate back to the PubNub event listener that was defined earlier. The purpose of the event listener for this app is to take the data updates published to the channel and update the state variables.
In order to update the users
map, initialize a copy of the mapping to manipulate.
this.pubnub.getMessage("channel", msg => { let users = this.state.users; });
Inside the event listener, determine if the incoming message is a request from a user to hide their GPS data and remove them from the mapping accordingly.
if (msg.message.hideUser) { users.delete(msg.publisher); this.setState({ users }); }else{ /*something else*/ }
Otherwise, the message contains data updates for a user and a new user (with the updated values) must be initialized to replace the old one.
else{ coord = [msg.message.latitude, msg.message.longitude]; //Format GPS Coordinates for Payload let oldUser = this.state.users.get(msg.publisher); let newUser = { uuid: msg.publisher, latitude: msg.message.latitude, longitude: msg.message.longitude, }; if(msg.message.message){ Timeout.set(msg.publisher, this.clearMessage, 5000, msg.publisher); newUser.message = msg.message.message; }else if(oldUser){ newUser.message = oldUser.message } users.set(newUser.uuid, newUser); this.setState({ users }); }
You have just finished implementing the receiving end of updating a user’s data. You will now implement the sending end where the user will actually publish messages with their data updates.
In order to know when a user has changed one of their data variables, an event handler is used to detect these changes. React’s componentDidUpdate
function is used, which triggers anytime there is a change in the app’s data.
Specify the componentDidUpdate function to pass in the previous props and state. Check whether the user has toggled their allowGPS
and focusOnMe
variables and make the necessary changes to the app’s function and state.
componentDidUpdate(prevProps, prevState) { if (prevState.allowGPS != this.state.allowGPS) { //check whether the user just toggled their GPS settings if (this.state.allowGPS) { //if user toggled to show their GPS data, we add them to the user Map once again if (this.state.focusOnMe) { //if user toggled to focus map view on themselves this.animateToCurrent(this.state.currentLoc, 1000); } let users = this.state.users; //make a copy of the users array to manipulate //create a new user object with updated user values to replace the old user let tempUser = { uuid: this.pubnub.getUUID(), latitude: this.state.currentLoc.latitude, longitude: this.state.currentLoc.longitude, image: this.state.currentPicture, username: this.state.username }; users.set(tempUser.uuid, tempUser); this.setState( //quickly update the user Map locally { users }, () => { this.pubnub.publish({ //publish updated user to update everyone's user Map message: tempUser, channel: "channel" }); } ); } else { //if user toggled to hide their GPS data let users = this.state.users; let uuid = this.pubnub.getUUID(); users.delete(uuid); //delete this user from the user Map this.setState({ //update the userMap users, }); this.pubnub.publish({ //let everyone else's user Map know this user wants to be hidden message: { hideUser: true }, channel: "channel" }); } } }
You may have noticed a redundancy in this code snippet. Why is the state of the updated user being set locally and then publishing the updated user object to the channel? Isn’t that setting the state twice?
While the assumption is correct, there is a reason behind this action. The state is first updated locally so you can update the user’s screen as fast as possible for accurate location tracking. Then, the updated user object is published to the channel so that everyone else on the network can update their state as well.
You'll now implement the UI of the application in the render
function of the App.js file. First, implement the user map as a usable array.
let usersArray = Array.from(this.state.users.values());
Inside the return
, render the map component from React-Native-Maps by setting the initial region with some starting coordinates. You'll then iterate through the map to begin rendering every user on the network, and for each user, you'll render a marker component from React-Native-Maps as well as an image to represent that user.
return ( <View style={styles.container} > <MapView style={styles.map} ref={ref => (this.map = ref)} onMoveShouldSetResponder={this.draggedMap} initialRegion={{ latitude: 36.81808, longitude: -98.640297, latitudeDelta: 60.0001, longitudeDelta: 60.0001 }} > {usersArray.map((item) => ( <Marker style={styles.marker} key={item.uuid} //distinguish each user's marker by their UUID coordinate={{ //user's coordinates latitude: item.latitude, longitude: item.longitude }} ref={marker => { this.marker = marker; }} > <Image style={styles.profile} source={require('./LOCATION OF YOUR USER IMAGE PROFILES')} //User's image /> </Marker>))} </MapView> </View> );
Below the MapView
, you can define a toggle switch for the user to toggle their allowGPS
state.
<View style={styles.topBar}> <View style={styles.rightBar}> <Switch value={this.state.allowGPS} style={styles.locationSwitch} onValueChange={this.toggleGPS} /> </View> </View>
Finally, add a button to enter the map on the user.
<View style={styles.bottom}> <View style={styles.bottomRow}> <TouchableOpacity onPress={this.focusLoc}> <Image style={styles.focusLoc} source={require('./heart.png')} /> </TouchableOpacity> </View> </View>
Add the following to style your components.
const styles = StyleSheet.create({ bottomRow:{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, marker: { justifyContent: "center", alignItems: "center", marginTop: Platform.OS === "android" ? 100 : 0, }, topBar: { top: Platform.OS === "android" ? hp('2%') : hp('5%'), flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginHorizontal: wp("2%"), }, rightBar: { flexDirection: "row", justifyContent: "flex-end", alignItems: "center" }, leftBar: { flexDirection: "row", justifyContent: "flex-start", alignItems: "center" }, locationSwitch: { left: 300, }, container: { flex: 1 }, bottom: { position: "absolute", flexDirection:'column', bottom: 0, justifyContent: "center", alignSelf: "center", width: "100%", marginBottom: hp("4%"), }, focusLoc: { width: hp("4.5%"), height: hp("4.5%"), marginRight: wp("2%"), left: 15 }, userCount: { marginHorizontal: 10 }, map: { ...StyleSheet.absoluteFillObject }, profile: { width: hp("4.5%"), height: hp("4.5%") }, });
For Android devices to be compatible with this application, add the following in the beginning of the setUpApp
function.
let granted; if (Platform.OS === "android"){ granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION , { title: 'Location Permission', message: 'PubMoji needs to access your location', buttonNegative: 'No', buttonPositive: 'Yes', }); }
You will also need to add an if
statement around the watchPosition
as follows.
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.OS === "ios") { /*-----watchPosition()----*/ } else { console.log( "ACCESS_FINE_LOCATION permission denied" ) }
You are now ready to run the application. You can simulate the app with iOS devices using the following command.
sudo react-native run-ios
For Android devices, you can simulate the application with the following command.
react-native run-android
Congratulations, you have created your own real-time geolocation app in React Native using PubNub! You're able to render multiple users on a map view, allow users to click a button to zoom the map on their location, and enable users to toggle their location permissions.
You can check out the completed version of the application on GitHub. If you would like to learn more about how to power your React applications with PubNub, be sure to check out PubNub's other React developer resources:
If you have any other questions or concerns, please feel free to reach out to devrel@pubnub.com.
There are common underlying technologies for a dating app, and in this post, we’ll talk about the major technologies and designs...
Michael Carroll
How to use geohashing, JavaScript, Google Maps API, and BART API to build a real-time public transit schedule app.
Michael Carroll
How to track and stream real-time vehicle location on a live-updating map using EON, JavaScript, and the Mapbox API.
Michael Carroll