Asher Scott

Geolocation with React Native Maps (Part 1)


Posted: Sept. 11, 2017 Category: Programming

Often times when implementing map-based functionality in mobile applications, being able to use the current global location of the user is necessary. Take for example, Google Maps. How would the application be able to get you, the user, from point A to point B if the application has no idea where point A is to begin with?  Pretty silly question right? Well the answer is, it wouldn't be able to. That's why when you open up an application like Google Maps for the first time, you'll see an alert message asking for permission to use your current location. The location data that you share with Google Maps makes the application incredibly useful, and allow for you to utilize some of its most excellent features, as is the case for many applications that request your current location. In this blog post, you aren't simply going to see an arbitrary example of how to use location services, but rather a walk-through of how I integrate location services to some of the functionality of my application. However, don't get me wrong, I entirely intend this post to be entirely educational at the core.

So a quick background description of the project to get you up to speed on what I am going to be doing:  I have recently been working on a mobile application called fisk. It's a work-in-progress, open-source mobile application for fisherman that allows them to save waypoints, keep a log of the fish that they catch, etc. Right now, I am working on implementing the feature of saving waypoints (Which will be the focus of this blog post). Waypoints are basically dropped pins on a map that indicate a location where a user might have caught a personal best fish, or where they had a really fun day of fishing.

The picture of the app here shows the current Waypoints screen. This screen is basically going to be an interactive MapView component that shows where the user is currently located, and show waypoints, represented as dropped pins on the map, that are nearby the user. In addition to these functional intentions, the user should be able to click the add button on the right side of the header which will pull up a modal or alert asking if the user wants to drop a waypoint pin at the user's current location. In part 1 of this blog post, I think that getting the user's current location showing up on the map would be a great first milestone for this feature implementation.  

In order to get the user's current location data, I need to use the Permissions API according to this documentation. The permissions API is what we are going to use to get permission to use a user's location data, our request for permissions is going to be in the form of an alert message that asks for the user's confirmation to allow us to use their location.

I am going to be writing the code that asks for permissions in my main App.js file, specifically in the async componentWillMount method.

Here is the source code for my App.js componentWillMount method so far:

async componentDidMount() {
    await Font.loadAsync({
      'gudea': require('./assets/fonts/Gudea-Regular.ttf'),
      'gloria': require('./assets/fonts/GloriaHallelujah.ttf'),
    });
   
    this.setState({ appIsReady: true });
 }

Currently, all this method does is load some external fonts that I use in the application.  Nothing fancy, just a basic async load.

Now, what I want to do is write the code to ask for the user's permission for the location services, and if that permission is granted, I want to store the user's location in the application's state for the time being. First, were going to need to import a few things from Expo, so I add the following to the top of my App.js file:

import { Location, Permissions } from 'expo';

and then I add the following to the componentDidMount method:

async componentDidMount() {
    await Font.loadAsync({
      'gudea': require('./assets/fonts/Gudea-Regular.ttf'),
      'gloria': require('./assets/fonts/GloriaHallelujah.ttf'),
    });

    let { status } = await Permissions.askAsync(Permissions.LOCATION);
    if(status === 'granted') {
      let location = await Location.getCurrentPositionAsync({});
      this.setState({ location });
    }


    this.setState({ appIsReady: true });
}

Okay, so let's go over what's going on in these few lines of code, it's rather trivial.  

let { status } = await Permissions.askAsync(Permissions.LOCATION);

Here am calling the askAsync method from Expo's Permissions module. The askAsync method takes a permissions type as input and prompts the user for permission to use services associated with the passed in permission type, which in this case we are passing in Permissions.LOCATION. If permission is already granted, the method will return success, otherwise it will require the user's action to determine whether the permission is granted or not.

if(status === 'granted') {
      let location = await Location.getCurrentPositionAsync({});
      this.setState({ location });
    }

Here I am checking to see if permission was granted by the user.  If so, I call the getCurrentPositionAsync method from Expo's Location module. This method takes in an options object, which can contain fields as defined here. I am not passing in any options for this method at the moment, though I may do so later. The method returns an object that contains values such as the current latitude and longitude of the user's device. This object is then stored in the Application's state object.

So now that we have the user's location data, let's get the application rendering the user's location on MapView component of the the Waypoint screen.

Because I am storing the user's location in the application root's (App.js) state, I am going to have to pass the user's location as a property to the Navigation that handles the rendering of my Waypoint's screen. This is done by passing the location object from our state to the screenProps of my navigator, which is shown below:

render() {
    if (this.state.appIsReady) {
      return (
        <View style={styles.container}>
          <StatusBar barStyle="light-content" />
          <View style={styles.body}>
            <TabNav screenProps={this.state.location} style={{backgroundColor: "#121212"}} />
          </View>
        </View>
      );
    } else {
      return <AppLoading />
    }
  }


Now I should be able to access the user's location from my Waypoints screen:

render() {
const region = {
            latitude: this.props.screenProps.coords.latitude,
            longitude: this.props.screenProps.coords.longitude,
            latitudeDelta: 0.0030,
            longitudeDelta: 0.0030,
        };


        const waypoints = this.state.waypoints.map((waypoint, i) => {
            return <MapView.Marker key={i} coordinate={waypoint} />
        })

return (
            <View>
                <MapView 
                    mapType={this.state.mapType} 
                    initialRegion={region} 
                    height={560}
                    showsUserLocation={true} 
                    followUserLocation={true}>
                </MapView>
            </View>
        )
}

Here I am defining a region object that gets passed to a react-native-maps MapView component, and setting the latitude and longitude values to the user's location that we acquired in the componentDidMount method in App.js, and then passing that region object to the MapView's initialRegion property.

If everything works correctly, my Waypoints screen should show the region that the user is currently located at, as well as the nifty blue circle marker that pinpoints the user's precise location.

*Note: The rendering of the blue location marker is due to the showsUserLocation property being set to true, react-native-maps has built in functionality to get the user's current location and render the blue marker, the position of this marker has nothing to do with the region object that we created.

Let's see if it worked:


Cool! The region that the map is displaying is where my personal device is located, the blue marker is where I am currently located at the time of writing this post!  So it looks as if everything is working correctly!  This functionality is exactly what I wanted to accomplish in this first post.  

Remember, this application is open source, I want you to be able to learn from this project as much as I am learning from it. Therefore, you can see the code changes that I made for this implementation in the diff on the official GitHub repository for this application here.  

Next time, in part 2 of this post, I intend to implement functionality that updates the map's region as the user's location changes. As you can probably imagine, this functionality is really important in the case that a fisherman is moving in a boat or strolling along a shoreline searching for fishing hotspots.

Take me to Part 2!