Learn how to build a location-sharing app using the geolocation functionality in React Native and broadcast geolocation data in realtime with Pusher.
In this tutorial, we’ll be building a location-sharing app with React Native and Pusher. By reading this tutorial, you will learn how to use the geolocation functionality in React Native, and broadcast the geolocation data with Pusher. You will also learn how to integrate Facebook login into the app.
You will need the following in order to follow the tutorial:
In the following sections, I’ll be showing you how to create the Pusher, Facebook, and Google project.
Once you’re logged in to your Pusher account, go to your Dashboard and look for the menu for creating a new app. Set the name of the app to “locSharer”, and select the cluster nearest to your location. Click on the Create my app button to create the app. Once the app is created, click on the App Settings tab and enable Client Events. We need this because we’ll be sending events directly from the app. After that, click on the App Keys tab and copy the credentials somewhere where you can easily access it later on. We’ll be needing it later once we start configuring the app.
The minimum requirement for creating a Facebook app is for you to have a Facebook account. Once you’re logged in to your account, go to the Facebook developers website and create a new app. Set the Display Name to “locSharer”. Once the app is created, add Android as a platform then set the following details:
– Google Play Package Name: com.locsharer
– Class Name: com.locsharer.MainActivity
Next, generate a key hash to be used for development:
1keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
The command above generates a 28-character key hash. Paste the value under the Key Hashes field and save the changes. This step ensures the authenticity of the interactions between your app and Facebook, thus it’s a required step even for the development environment. You can find more information about this on the Facebook login documentation for Android.
Just like Facebook, you need to have a Google account in order to create a Google project. Once you’re logged in to your Google account, go to the Google Developer Console and create a project. Set the project name to “locSharer”. Once the project is created, click on Enable APIs and Services button. From there, look for Google Maps Android API and enable it. Next, click on the Credentials tab and create an API key. Once the key is created, it will ask you to restrict access. Set the key restriction to Android. Then you can use the same keystore you used for Facebook:
1keytool -list -v -keystore ~/.android/debug.keystore
The command above allows you to get the sha1 hash. Look for it, copy the corresponding value and paste it under the SHA-1 certificate fingerprint field. Also, enter the package name of the app (com.locsharer) then save the changes.
As mentioned earlier, we will be creating a location-sharing app. First the user has to login with their Facebook account:
Once logged in, the user can enable location-sharing so that their friends can see their current location when they view them:
If the user has friends who are also using the app, they will be listed below the user’s details. Tapping on a friend will display a map which gets updated based on their current location (but only if they have enabled location sharing). The current location is indicated by a marker:
You can find the source code for this project on its Github repo.
Just like every other Pusher app integration, this app needs a server component as well. The server’s job is to authenticate the requests coming from the app. This allows us to make sure that the request is indeed coming from the app and not anywhere else.
Start by creating a new folder for the server-related files. Inside the folder, run npm init
to initialize a new project. Simply press enter until it asks you to confirm the project details. Once you get to that, just respond with yes.
Next, install the packages that we’ll be needing:
1npm install --save express body-parser pusher
Once the packages are installed, create a server.js
file. Start by including the packages we just installed:
1var express = require('express'); 2 var bodyParser = require('body-parser'); 3 var Pusher = require('pusher'); 4 5 var app = express(); 6 app.use(bodyParser.json()); 7 app.use(bodyParser.urlencoded({ extended: false }));
Next, add the code for connecting to Pusher. The Pusher app credentials are being loaded as environment variables. As you have seen from the code below, we’re not really using a module for loading environment variables from a .env
file. Later I’ll show you how the values are being supplied.
1var pusher = new Pusher({ 2 appId: process.env.APP_ID, 3 key: process.env.APP_KEY, 4 secret: process.env.APP_SECRET, 5 cluster: process.env.APP_CLUSTER, 6 });
Add a route for verifying if the server is really working:
1app.get('/', function(req, res){ 2 res.send('server is running'); 3 });
Add the code for authenticating users that are connecting to your Pusher app. This contains the unique key that we will use later on to check whether the request has indeed come from the app.
1app.post('/pusher/auth', function(req, res) { 2 var socketId = req.body.socket_id; 3 var channel = req.body.channel_name; 4 var auth = pusher.authenticate(socketId, channel); 5 var app_key = req.body.app_key; 6 if(app_key == process.env.UNIQUE_KEY){ 7 var auth = pusher.authenticate(socketId, channel); 8 res.send(auth); 9 } 10 11 res.send(auth); 12 });
Initiate the server on the port set in the environment variables. Normally this would be served on port 80:
1var port = process.env.PORT || 5000; 2 app.listen(port);
The server needs to be accessible via the internet. One service that allows us to do this for free is Now. **You can install Now **globally with the following command:
1npm install -g now
Once installed, you can now add your Pusher app credentials as a secret. One caveat of Now is that all the files for the deployed projects are available publicly. This means that the values in the .env
files are publicly available as well. Adding those values as a secret means that it won’t be accessible anywhere.
1now secret add locshare_app_id YOUR_PUSHER_APP_ID 2 now secret add locshare_app_key YOUR_PUSHER_APP_KEY 3 now secret add locshare_app_secret YOUR_PUSHER_APP_SECRET 4 now secret add locshare_app_cluster YOUR_PUSHER_APP_CLUSTER 5 now secret add locshare_unique_key YOUR_UNIQUE_KEY
Don’t forget to replace the values with your actual Pusher app credentials.
Once that’s done, you can deploy the server:
1now -e APP_ID=@locshare_app_id -e APP_KEY=@locshare_app_key -e APP_SECRET=@locshare_app_secret APP_CLUSTER=@locshare_app_cluster -e UNIQUE_KEY=@locshare_unique_key
What the command above does is deploy the server, as well as setting the environment variables using the -e
option. The secret values that you’ve added earlier are accessed by using the @
sign. When the process is completed, it should return a URL pointing to the server. Access that in the browser to check whether the server is running or not.
It’s now time to create the actual app. Start by generating a new React Native project:
1react-native init LocSharer
Next, install the dependencies of the app:
1npm install --save prop-types pusher-js react-native-facebook-login react-native-maps react-navigation
Here’s a brief overview of what each package does:
– prop-types – for specifying the intended types of properties passed to components.
– pusher-js – for interacting with Pusher.
– react-native-facebook-login – for implementing Facebook login.
– react-native-maps – for displaying Google Maps and markers.
– react-navigation – for implementing Stack navigation within the app.
Additional steps are required in order for Facebook login and Google Maps to work. We’ll look at how to do that in the sections to follow.
Configuring Facebook Login
The following steps assume that you have already created a Facebook app. So create one, if you haven’t done so already.
Once you’ve created a Facebook app, open the android/settings.gradle
file and add the following to the bottom of the file:
1include ':react-native-facebook-login' 2 project(':react-native-facebook-login').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-facebook-login/android')
Next, open the android/app/build.gradle
file and add the following inside the dependencies
:
1dependencies { 2 ... 3 compile project(':react-native-facebook-login') 4 }
Next, register the React package by opening the android/app/src/main/java/com/{YOUR PACKAGE NAME}/MainApplication.java
file, and adding the following:
1// top of the file 2 import com.magus.fblogin.FacebookLoginPackage; // <--- add this 3 4 public class MainApplication extends Application implements ReactApplication { 5 6 ... 7 8 @Override 9 protected List<ReactPackage> getPackages() { 10 return Arrays.<ReactPackage>asList( 11 new MainReactPackage(), 12 new FacebookLoginPackage() // <--- add this 13 ); 14 } 15 16 ... 17 }
Next, open the android/app/src/main/res/values/strings.xml
file and add the details of the Facebook app you created earlier:
1<resources> 2 <string name="app_name">{YOUR FACEBOOK APP NAME}</string> 3 <string name="fb_app_id">{YOUR FACEBOOK APP ID}</string> 4 <string name="fb_login_protocol_scheme">fb{YOUR FACEBOOK APP ID}</string> 5 </resources>
Lastly, open the android/app/src/main/AndroidManifest.xml
file and add the following:
1<manifest 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" <-- add this 4 package="com.your.app.namespace"> 5 6 <application 7 ... 8 9 <!--add FacebookActivity--> 10 <activity 11 tools:replace="android:theme" 12 android:name="com.facebook.FacebookActivity" 13 android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" 14 android:label="@string/app_name" 15 android:theme="@android:style/Theme.Translucent.NoTitleBar"/> 16 17 <!--add CustomTabActivity--> 18 <activity 19 android:name="com.facebook.CustomTabActivity" 20 android:exported="true"> 21 <intent-filter> 22 <action android:name="android.intent.action.VIEW" /> 23 <category android:name="android.intent.category.DEFAULT" /> 24 <category android:name="android.intent.category.BROWSABLE" /> 25 <data android:scheme="@string/fb_login_protocol_scheme" /> 26 </intent-filter> 27 </activity> 28 29 <!--add reference to Facebook App ID--> 30 <meta-data 31 android:name="com.facebook.sdk.ApplicationId" 32 android:value="@string/fb_app_id"/> 33 34 </application> 35 </manifest>
The following steps assume that you have already created a Google project, and generated an API key.
Start by linking the package resources to your app:
1react-native link react-native-maps
Open the android\app\src\main\AndroidManifest.xml
file and add a reference to your Google project’s API key:
1<application> 2 ... 3 <meta-data 4 android:name="com.google.android.geo.API_KEY" 5 android:value="YOUR GOOGLE PROJECT'S ANDROID API KEY"/> 6 </application>
Also, add the following below the default permissions:
1<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> 2 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Now we’re ready to actually code the app. Start by opening the index.js
file and replace the default contents with the following:
1import { AppRegistry } from 'react-native'; 2 import App from './App'; 3 4 AppRegistry.registerComponent('LocSharer', () => App);
The entry point of the app will be the App
component. To create an App.js
file and add the following:
1import React, { Component } from 'react'; 2 import { StackNavigator } from 'react-navigation'; 3 4 import IndexPage from './src/components/index'; 5 import MapPage from './src/components/map_page'; 6 7 const Page = StackNavigator({ 8 Home: { screen: IndexPage }, 9 MapPage: { screen: MapPage }, 10 }); 11 12 export default class App extends Component<{}> { 13 14 render() { 15 return <Page /> 16 } 17 }
The code above uses the React Navigation library to create a StackNavigator. This allows the app to transition from one screen to another by placing the new screen on top of the stack. This allows us to easily implement the back functionality since all it has to do is to “pop” the current screen out of the stack in order to go back to the previous screen. To use the StackNavigator, pass in the components to be used as the individual pages. The first one is the initial screen of the app.
Next, create a src
directory and inside create an index.js
file. This will serve as the initial page of the app. First, import the modules and components that we need:
1import React, { Component } from 'react'; 2 import { 3 StyleSheet, 4 Text, 5 View, 6 Switch, // for toggling location sharing on and off 7 DeviceEventEmitter // for emitting/listening custom events 8 } from 'react-native'; 9 10 var { FBLogin } = require('react-native-facebook-login'); // for implementing Facebook login 11 12 import Pusher from 'pusher-js/react-native'; // for interacting with Pusher 13 14 import Profile from './profile'; // component for displaying the user's profile 15 import Friends from './friends'; // component for displaying the user's friends 16 17 import { regionFrom } from '../helpers'; // helper function for constructing the data needed by React Native Maps
Create the actual component:
1export default class Index extends Component<{}> { 2 // set the title of the screen 3 static navigationOptions = { 4 title: 'LocSharer', 5 }; 6 }
In the constructor, we bind the functions to be used throughout the class as well as setting the default state:
1constructor() { 2 3 super(); 4 5 this.watchId = null; // unique ID for the geolocation watcher 6 this.pusher = null; // variable for storing the Pusher instance 7 this.user_channel = null; // the Pusher channel for the current user 8 9 // bind the functions to the class 10 this.onLogin = this.onLogin.bind(this); 11 this.onLoginFound = this.onLoginFound.bind(this); 12 this.onLogout = this.onLogout.bind(this); 13 this.setUser = this.setUser.bind(this); 14 this.setFriends = this.setFriends.bind(this); 15 this.toggleLocationSharing = this.toggleLocationSharing.bind(this); 16 this.onViewLocation = this.onViewLocation.bind(this); 17 18 this.state = { 19 is_loggedin: false, // whether the user is currently logged in or not 20 is_location_shared: false, // whether the user is currently sharing their location or not 21 user: null, // data for the currently logged in user 22 friends: null, // data for the user's friends 23 subscribed_to: null, // the Facbook user ID of the user's friend whose location is currently being viewed 24 subscribed_friends_count: 0 // number of friends currently subscribed to the user 25 }; 26 27 }
The onLogin()
function is executed when the user has logged in with Facebook. Some of the user’s data such as the ID, access token, and name are passed in as an argument to this function. It is then used to set the user’s and friends’ data on the state using two functions:
1onLogin(login_data) { 2 this.setUser(login_data); 3 this.setFriends(login_data.credentials.token); 4 }
The onLoginFound()
function is executed if an existing Facebook session is already present. The arguments passed into this function are limited so we have to make a separate API request to get the user’s name:
1onLoginFound(data) { 2 3 let token = data.credentials.token; 4 5 fetch(`https://graph.facebook.com/me?access_token=${token}`) 6 .then((response) => response.json()) 7 .then((responseJson) => { 8 9 let login_data = { 10 profile: { 11 id: responseJson.id, 12 name: responseJson.name 13 }, 14 credentials: { 15 token: token 16 } 17 }; 18 19 this.setUser(login_data); 20 }) 21 .catch((error) => { 22 console.log('something went wrong', error); 23 }); 24 25 this.setFriends(token); 26 27 }
Here’s the function for setting the data for the current user. All it does is the format the login data returned by the Facebook API and set it on the state:
1setUser(login_data) { 2 3 let user_id = login_data.profile.id; 4 this.setState({ 5 is_loggedin: true, 6 user: { 7 id: user_id, 8 access_token: login_data.credentials.token, 9 name: login_data.profile.name, 10 photo: `https://graph.facebook.com/${user_id}/picture?width=100` // the user's profile picture 11 } 12 }); 13 14 }
The setFriends()
function makes a request to the Facebook API to get the array of the user’s friends:
1setFriends(token) { 2 fetch(`https://graph.facebook.com/me/friends?access_token=${token}`) 3 .then((response) => response.json()) 4 .then((responseJson) => { 5 this.setState({ 6 friends: responseJson.data 7 }); 8 }) 9 .catch((error) => { 10 console.log('something went wrong', error); 11 }); 12 }
Once the user logs out, destroying the session data is already taken care of by the Facebook login package. So all we have to do is unset all the user data that we’ve set earlier:
1onLogout() { 2 this.setState({ 3 is_loggedin: false, 4 user: null, 5 friends: null, 6 is_subscribed_to: null 7 }); 8 }
Next, initialize Pusher. Be sure to replace the placeholder values with your Pusher app details. We’re also passing in an auth parameter. This is the request data that we were checking earlier in the server code. Simply pass in the same unique string that you’ve used earlier when you added the secret:
1componentWillMount() { 2 this.pusher = new Pusher('YOUR PUSHER APP ID', { 3 authEndpoint: 'YOUR AUTH SERVER AUTH ENDPOINT', 4 cluster: 'YOUR APP CLUSTER', 5 encrypted: true, 6 auth: { 7 params: { 8 app_key: 'YOUR UNIQUE KEY', // <-- should be the same as the unique key you added as a secret using now 9 } 10 } 11 }); 12 13 // add code for listening for the unsubscribe event 14 15 }
Next, we need a way to unsubscribe from a friend’s channel when the current user is no longer viewing their location on a map. That happens when the user goes back from the map page to the index page. The React Navigation library doesn’t really provide a way to listen for the event when the back button is tapped. That’s why we need a way to emulate that behavior. I’ll let you figure out your own solution. So feel free to skip the following paragraph if you want.
The solution I came up with is to use the DeviceEventEmitter
module. Add a listener for the unsubscribe
event, and once this is triggered, unsubscribe from the friend’s channel. The event is triggered from the map page when the user goes back to the index page:
1DeviceEventEmitter.addListener('unsubscribe', (e) => { 2 let friend_id = this.state.subscribed_to; 3 this.pusher.unsubscribe(`private-friend-${friend_id}`); 4 });
The toggleLocationSharing()
function is executed every time the user toggles the switch for sharing their location. If location sharing is enabled, we subscribe the user to their own channel. This allows them to listen for when one of their friends subscribes to their channel. When this happens, we begin watching the user’s current location and publish the data using Pusher. If the user decides to disable location sharing, we unsubscribe the user from their own channel and stop watching the location. This effectively stops the updating of location from their friend’s screens:
1toggleLocationSharing() { 2 3 let is_location_shared = !this.state.is_location_shared; 4 5 this.setState({ 6 is_location_shared: is_location_shared 7 }); 8 9 let user_id = this.state.user.id; 10 if(!is_location_shared){ 11 this.pusher.unsubscribe(`private-friend-${user_id}`); // disconnect from their own channel 12 if(this.watchId){ 13 navigator.geolocation.clearWatch(this.watchId); 14 } 15 }else{ 16 this.user_channel = this.pusher.subscribe(`private-friend-${user_id}`); 17 this.user_channel.bind('client-friend-subscribed', (friend_data) => { 18 19 let friends_count = this.state.subscribed_friends_count + 1; 20 this.setState({ 21 subscribed_friends_count: friends_count 22 }); 23 24 if(friends_count == 1){ // only begin monitoring the location when the first subscriber subscribes 25 this.watchId = navigator.geolocation.watchPosition( 26 (position) => { 27 var region = regionFrom( 28 position.coords.latitude, 29 position.coords.longitude, 30 position.coords.accuracy 31 ); 32 this.user_channel.trigger('client-location-changed', region); // push the data to subscribers 33 } 34 ); 35 } 36 }); 37 38 } 39 }
The onViewLocation()
function is executed when the user taps on any friend on their friend list. This is where we subscribe to the friend’s channel so we can get updates whenever their location changes:
1onViewLocation(friend) { 2 3 this.friend_channel = this.pusher.subscribe(`private-friend-${friend.id}`); 4 this.friend_channel.bind('pusher:subscription_succeeded', () => { 5 let username = this.state.user.name; 6 this.friend_channel.trigger('client-friend-subscribed', { 7 name: username 8 }); 9 }); 10 11 this.setState({ 12 subscribed_to: friend.id 13 }); 14 15 // add code for navigating to the map page 16 }
Next, add the code for navigating to the map page. Pass in the name of the friend and the reference to the friend’s channel as navigation props. This allows those values to be accessed from the map page later on:
1const { navigate } = this.props.navigation; 2 3 navigate('MapPage', { 4 name: friend.name, 5 friend_channel: this.friend_channel // pass the reference to the friend's channel 6 });
Render the index page. This consists of the user’s profile, their friendslist, and the Facebook login or logout button:
1render() { 2 3 return ( 4 <View style={styles.page_container}> 5 { 6 this.state.is_loggedin && 7 <View style={styles.container}> 8 { 9 this.state.user && 10 <View style={styles.profile_container}> 11 <Profile 12 profile_picture={this.state.user.photo} 13 profile_name={this.state.user.name} 14 /> 15 16 <Text>Share Location</Text> 17 <Switch 18 value={this.state.is_location_shared} 19 onValueChange={this.toggleLocationSharing} /> 20 </View> 21 } 22 23 { 24 this.state.friends && 25 <Friends 26 friends={this.state.friends} 27 onViewLocation={this.onViewLocation} /> 28 } 29 </View> 30 } 31 32 <FBLogin 33 permissions={["email", "user_friends"]} 34 onLogin={this.onLogin} 35 onLoginFound={this.onLoginFound} 36 onLogout={this.onLogout} 37 style={styles.button} 38 /> 39 </View> 40 ); 41 42 } 43 44 // add the styles 45 const styles = StyleSheet.create({ 46 page_container: { 47 ...StyleSheet.absoluteFillObject, 48 justifyContent: 'flex-end' 49 }, 50 container: { 51 flex: 1, 52 padding: 20 53 }, 54 profile_container: { 55 flex: 1, 56 alignItems: 'center', 57 marginBottom: 50 58 }, 59 button: { 60 paddingBottom: 30, 61 marginBottom: 20, 62 alignSelf: 'center' 63 } 64 });
The code above is pretty self-explanatory so I won’t go into details about what each line does.
The Profile component is used for displaying the user’s profile picture and name:
1import React, { Component } from 'react'; 2 import { 3 StyleSheet, 4 Text, 5 View, 6 Image 7 } from 'react-native'; 8 9 import PropTypes from 'prop-types'; 10 11 class Profile extends Component<{}> { 12 13 render() { 14 15 return ( 16 <View style={styles.profile_container}> 17 <Image 18 resizeMode={"contain"} 19 source={{uri: this.props.profile_picture}} 20 style={styles.profile_photo} 21 /> 22 <Text style={styles.profile_name}>{this.props.profile_name}</Text> 23 </View> 24 ); 25 26 } 27 28 } 29 30 const styles = StyleSheet.create({ 31 profile_container: { 32 alignItems: 'center' 33 }, 34 profile_photo: { 35 height: 100, 36 width: 100 37 }, 38 profile_name: { 39 fontWeight: 'bold', 40 fontSize: 18 41 } 42 }); 43 44 // specify the required props 45 Profile.propTypes = { 46 profile_picture: PropTypes.string.isRequired, 47 profile_name: PropTypes.string.isRequired 48 }; 49 50 export default Profile;
The Friends component is used for rendering the list of friends:
1import React, { Component } from 'react'; 2 import { 3 StyleSheet, 4 Text, 5 View, 6 Image, 7 TouchableHighlight 8 } from 'react-native'; 9 10 import PropTypes from 'prop-types'; 11 12 class Friends extends Component<{}> { 13 14 renderFriends() { 15 return this.props.friends.map((friend, index) => { 16 17 let profile_picture = `https://graph.facebook.com/${friend.id}/picture?width=50`; 18 return ( 19 <TouchableHighlight 20 key={index} 21 onPress={this.props.onViewLocation.bind(this, friend)} 22 underlayColor={"#CCC"}> 23 24 <View style={styles.friend_row}> 25 <Image 26 resizeMode={"contain"} 27 source={{uri: profile_picture}} 28 style={styles.profile_photo} 29 /> 30 <Text style={styles.friend_name}>{friend.name}</Text> 31 </View> 32 33 </TouchableHighlight> 34 ); 35 }); 36 } 37 38 render() { 39 40 return ( 41 <View style={styles.friends_container}> 42 <Text style={styles.friends_header_text}>View Friend Location</Text> 43 {this.renderFriends.call(this)} 44 </View> 45 ); 46 47 } 48 } 49 50 // add the styles 51 const styles = StyleSheet.create({ 52 friends_container: { 53 flex: 2 54 }, 55 friends_header_text: { 56 fontSize: 18, 57 fontWeight: 'bold' 58 }, 59 friend_row: { 60 flexDirection: 'row', 61 alignItems: 'center', 62 padding: 10 63 }, 64 profile_photo: { 65 width: 50, 66 height: 50, 67 marginRight: 20 68 }, 69 friend_name: { 70 fontSize: 15 71 } 72 }); 73 74 // specify the required props 75 Friends.propTypes = { 76 friends: PropTypes.arrayOf( 77 PropTypes.shape({ 78 id: PropTypes.string.isRequired, 79 name: PropTypes.string.isRequired 80 }) 81 ), 82 onViewLocation: PropTypes.func.isRequired 83 }; 84 85 export default Friends;
Earlier, we’ve used a function called regionFrom
but we haven’t really created it yet. So go ahead and create a src/helpers.js
file and add the following:
1export function regionFrom(lat, lon, accuracy) { 2 const oneDegreeOfLongitudeInMeters = 111.32 * 1000; 3 const circumference = (40075 / 360) * 1000; 4 5 const latDelta = accuracy * (1 / (Math.cos(lat) * circumference)); 6 const lonDelta = (accuracy / oneDegreeOfLongitudeInMeters); 7 8 return { 9 latitude: lat, 10 longitude: lon, 11 latitudeDelta: Math.max(0, latDelta), 12 longitudeDelta: Math.max(0, lonDelta) 13 }; 14 }
This function is used for getting the latitude and longitude delta values needed by the React Native Maps library to display a map.
Now we move over to the map page. Create a src/map_page.js
file and add the following:
1import React, { Component } from 'react'; 2 import { 3 StyleSheet, 4 Text, 5 View, 6 DeviceEventEmitter 7 } from 'react-native'; 8 9 import Map from './map'; 10 11 import { regionFrom } from '../helpers'; 12 13 // add code for creating the component
Create the component, and set the page title based on the parameters passed from the index page:
1export default class MapPage extends Component<{}> { 2 3 static navigationOptions = ({navigation}) => ({ 4 title: `${navigation.state.params.name}'s Location`, 5 }); 6 7 // add constructor code 8 }
Set a default location in the constructor so that a map is still displayed even if the user is not sharing their location:
1constructor() { 2 super(); 3 4 // set default location 5 let region = { 6 "latitude": 35.4625901, 7 "longitude": 138.65437569999995, 8 "latitudeDelta": 0, 9 "longitudeDelta": 0 10 }; 11 12 this.state = { 13 region 14 } 15 }
When the user taps on the back button, componentWillUnmount()
is triggered as the component goes out of view. So this is the perfect time to trigger the unsubscribe
event to let the index page know that the user has stopped viewing their friend’s location.
1componentWillUnmount() { 2 DeviceEventEmitter.emit('unsubscribe', { 3 unsubscribe: true 4 }); 5 }
When the component is mounted, we want to start listening for when the location changes so we can update the map accordingly:
1componentDidMount() { 2 3 const { state } = this.props.navigation; 4 state.params.friend_channel.bind('client-location-changed', (data) => { 5 this.setState({ 6 region: data 7 }); 8 }); 9 10 }
The render()
method simply outputs the Map component:
1render() { 2 3 return ( 4 <View style={styles.map_container}> 5 { 6 this.state.region && 7 <Map region={this.state.region} /> 8 } 9 </View> 10 ); 11 12 }
Add the styles:
1const styles = StyleSheet.create({ 2 map_container: { 3 ...StyleSheet.absoluteFillObject, 4 justifyContent: 'flex-end' 5 } 6 });
Lastly, there’s the Map component which is used to actually render the Google Map. This uses the React Native Maps package that we installed earlier. There are only two components that you need in order to make it work: MapView
and MapView.Marker
. MapView
is used to render the map, and MapView.Marker
is used to rendering the marker:
1import React, { Component } from 'react'; 2 import { 3 StyleSheet, 4 View 5 } from 'react-native'; 6 7 import MapView from 'react-native-maps'; 8 import PropTypes from 'prop-types'; 9 10 class Map extends Component<{}> { 11 12 render() { 13 14 return ( 15 <View style={styles.map_container}> 16 { 17 this.props.region && 18 <MapView 19 style={styles.map} 20 region={this.props.region} 21 > 22 <MapView.Marker 23 coordinate={{ 24 latitude: this.props.region.latitude, 25 longitude: this.props.region.longitude}} 26 /> 27 </MapView> 28 } 29 </View> 30 ); 31 32 } 33 34 } 35 36 // add the styles 37 const styles = StyleSheet.create({ 38 map_container: { 39 ...StyleSheet.absoluteFillObject, 40 justifyContent: 'flex-end' 41 }, 42 map: { 43 ...StyleSheet.absoluteFillObject, 44 }, 45 }); 46 47 // specify the required props 48 Map.propTypes = { 49 region: PropTypes.shape({ 50 latitude: PropTypes.number.isRequired, 51 longitude: PropTypes.number.isRequired, 52 latitudeDelta: PropTypes.number.isRequired, 53 longitudeDelta: PropTypes.number.isRequired 54 }) 55 }; 56 57 export default Map;
You only need one device and one emulator in order to test the app. First, run the app on your device by executing react-native run-android
. Once the app is running, disconnect the device and open a Genymotion virtual device. Execute the same command again to run the app on the virtual device. Don’t forget to add another Facebook user, aside from your own Facebook account as a tester or developer under the Facebook app settings. You can do that by clicking on the Roles tab and searching for the user in there. Only Facebook users that are added in the app settings can log in. This is because the Facebook app is still unpublished.
Genymotion has a built-in functionality for spoofing the GPS coordinates. This will trigger the geolocation functionality in the app every time the location changes (either by pointing the marker on a different location on the map or searching for another place). That’s why it’s best to use Genymotion for testing the user who is broadcasting their location. If you’re going to follow this route, be sure to check out the documentation on how to install Google Services on Genymotion since the Google Map functionality uses Google Services.
If you don’t have any device to test on, you can use Genymotion and the Pusher debug console to test the app. All you have to do is figure out the Facebook user ID of the two users you’re using for testing. You can do that by using this tool. Login with your Facebook account on Genymotion then click on one of the other accounts. You can then emulate the location update by manually entering the coordinates on the debug console. You can access the debug console from your Pusher app’s dashboard:
You can use the following as initial values:
private-friend-YOUR-ACCOUNTS-FB-ID
client-location-changed
1{ 2 "latitude": 16.6105538, 3 "longitude": 120.31429539999999, 4 "latitudeDelta": 0, 5 "longitudeDelta": 0 6 }
Send the event once that’s done. Sending the event should update the map on the app. You can use a service such as latlong.net to come up with the coordinates of different places.
If you want to improve the app, here are some ideas on what you can add:
– The number of friends that are currently viewing the user’s location doesn’t actually get updated when someone disconnects from the user’s channel. You can add a listener for when someone disconnects so that you can update the value as well.
– The current user doesn’t actually know who are the people that are currently subscribed to their location. For this, you can use an alert dialog everytime someone subscribes to the channel. The client-friend-subscribed
event has already been laid out for this purpose. You can even take the idea further by making use of Presence channels. This comes with an additional feature that allows you to keep track of the people that are subscribed to a specific channel.
– Add notifications to inform the subscribed users for when the user they’re subscribed to disables location sharing.
That’s it! In this tutorial you’ve learned how to create a location-sharing app which uses React Native’s built-in Geolocation library and Pusher to broadcast the data to the user’s friends. You can check out the project’s complete source code in its Github repo.