How to build a location based app in React Native - Part 1

Hrishi Mittal

Hrishi Mittal

I want to build a mobile app called Walkwaves for exploring the city. When I walk around London, I want the app to tell me what interesting buildings or monuments are nearby.

London is such a historic city, I think it would be a really fun way to explore it. Ideally, I'd like to just have the app running in the background and tell me (via text to speech) when I'm near an interesting place.

I'm writing this series of tutorials to show how I'm building this app and hopefully along the way, I can help you learn some React Native. I'm myself learning it as I build.

I already know React, so the tutorials assume some basic knowledge of React.

The whole app is quite complex, so I'm splitting it into many tutorials. In this first tutorial we're just going to build a simple geocoder app which can take a set of location co-ordinates (latitude, longitude) and convert them into a human understandable address or place name.

This is what we're going to build in this tutorial:

Geocoding app in React Native



Let's go!

Resources

The full code is on Github - https://github.com/learnetto/walkwaves

What I used to learn everything in this tutorial:

The Official React Native documentation

React Native Elements documentation

The OpenCage API documentation

Step 1: Setup

First, let's get setup. Make sure you have Node installed.

The official docs say that Expo is the easiest way to get started, so that's what I'm going to do.

Install the Expo CLI command line utility:

#Run this on the command line

npm install -g expo-cli

At the time of writing this tutorial, the latest React Native version of 0.57.

Step 2: Initialise a new app

Next, let's create our new app Walkwaves (you can call yours whatever you want!):

#Run this on the command line

expo init Walkwaves
? Choose a template: (Use arrow keys)
❯ blank
  The Blank project template includes the minimum dependencies to run and an empty root component. 
  tabs
  The Tab Navigation project template includes several example screens.

Choose the blank template.

Step 3: Start the development server

Then cd into the directory and start the development server.

#Run this on the command line

cd Walkwaves
expo start

This will output something like this on the terminal:
Expo development server starting

Step 4: Run the app on your phone or a simulator

Using one of the methods shown above, launch the app. I'm running it on an iPhone XS by scanning the QR code with the camera app which prompts you to open it in the Expo iPhone app. This is what it looks like:
Default React Native app screen


This is what the code directory structure and App.js looks like:
App.js
If you know any React, this will look very familiar to you. We import some components from the react and react-native libraries and then define the App component.

App is a simple React component which renders some text on the screen, using the Text component (which is like a paragraph <p> in HTML) inside the View component (which is like a div).

Step 5: Add a form for entering co-ordinates and geocoding them

We want a text field where we can type in the latitude and longitude, a button to trigger the geocoding (the technical term for conversion to an address), and a Text component to display the result. Of course, our goal is to display it all on a map, but this is just a very simplified first step towards that.

We'll need to import the TextInput and Button components and change the App component as follows:

//App.js

import { StyleSheet, Text, View, TextInput, Button } from 'react-native';

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Walkwaves</Text>
        <TextInput />
        <Button title='Geocode' />
        <Text>Human understandable address</Text>
      </View>
    );
  }
}

And now the app looks like this:

Unstyled screen for geocoding

That doesn't look great, does it? It can do with some styling.

Step 6: Style the components

We can style the default components directly but I found that it's not straightforward. For example, I wasted a lot of time trying to add a background colour to the Button, which is not directly supported (in the current version 0.57).

The React Native Elements library looks nice and provides easy ways to style elements, so I'm going to use that. 

Let's add the library to our app:

#Run this on the command line

yarn add react-native-elements

Now let's replace the default components with components from this library and add some styles:

//App.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements'

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>Walkwaves</Text>
        <FormLabel style={{padding: 20}}>Enter Co-ordinates (latitude, longitude)</FormLabel>
        <FormInput containerStyle={styles.input} />
        <Button title='Geocode'
          buttonStyle={styles.button}
          color='#FFFFFF'
        />
        <Text style={styles.result}>Human understandable address</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    marginTop: 100,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 30,
  },
  input: {
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 20,
    width: '80%',
  },
  button: {
    backgroundColor: '#1E88E5',
    width: 200,
  },
  result: {
    padding: 10,
  }
});

This looks much better:

Styled screen


We can tap on the input field and enter text. Let's store the entered input value in state so that we can use it later.

Let's add a constructor to the component and initialise the state with a value for the co-ordinates:

//App.js

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      coords: '',
      address: ''
    };
  }

We also initialise a value for the result of the conversion, called address.

Let's make sure the co-ordinates in state get updated when we type in the input box by setting the onChangeText prop on FormInput:

//App.js

        <FormInput containerStyle={styles.input}
          onChangeText={(coords) => this.setState({coords})} />

Let's also replace the text "Human understandable address" to use the value of the geocoded address from state:


//App.js

        <Button title='Geocode'
          buttonStyle={styles.button}
          color='#FFFFFF'
        />
        <Text style={styles.result}>{this.state.address}</Text>

Ok, now the we have a place to store the input and output values. But when we press the button, nothing happens yet...

So, now for the fun part!

Step 7: Add geocoding

I'm going to use the OpenCage Data API for geocoding the co-ordinates. To be more precise, it's reverse geocoding when you go from co-ordinates to addresses, and geocoding when it's the other way around.

OpenCage provides a very easy to use API and they have a generous free plan.

You can sign up here and get your API key.

Let's add the opencage-api-client library to our app:

#Run this on the command line

yarn add opencage-api-client

NOTE: At this step, I started getting a strange error from the fetch library (which is a dependency of opencage-api-client), saying "requiring module fetch which threw an exception...". The error goes away when I run the app in remote debug mode. See this discussion for some more workarounds. If you find a more elegant solution, please let me know.

Now, we need to trigger the geocoding when the button is pressed. We can do that by assigning a function - let's call it reverseGeocode - to the onPress prop of the Button component:

//App.js

        <Button title='Geocode'
          buttonStyle={styles.button}
          color='#FFFFFF'
          onPress={this.reverseGeocode}
        />

Now, let's import the library in App.js and define the function:

//App.js

import opencage from 'opencage-api-client';

export default class App extends React.Component {
  ...
  reverseGeocode = () => {
    const key = 'YOUR_API_KEY';
    opencage.geocode({ key, q: this.state.coords }).then(response => {
      result = response.results[0];
      this.setState({address: result.formatted});
    });
  }

Let's try it out now:

Without button loading animation


It works!

It can take a couple of seconds to make the API call to Opencage, get the result and then display it. It would be nice to have some visual feedback when the geocoding is going on.

Step 8: Add loading animation to the Button

The Button component has a handy prop called loading, using which we can display different text based on whether the button action is loading or not.

Let's first add another property to the state called geocoding, which will take a boolean value to indicate if geocoding is going on. We're initialise it as false:

//App.js

  constructor(props) {
    super(props);
    this.state = {
      coords: '31.79261,35.21785',
      address: '',
      geocoding: false
    };
  }

Now let's use it in reverseGeocode to set it to true before the API call and it back to false once the geocoding is done:

//App.js

  reverseGeocode = () => {
    const key = 'YOUR_API_KEY';
    this.setState({geocoding: true});
    opencage.geocode({ key, q: this.state.coords }).then(response => {
      result = response.results[0];
      console.log(result.formatted);
      this.setState({address: result.formatted, geocoding: false});
    });
  }


Now let's use it in the Button component to set the value of the loading prop and also to conditionally change the button text:

//App.js

        <Button
          loading={this.state.geocoding}
          buttonStyle={{backgroundColor: "blue"}}
          onPress={this.reverseGeocode}
          title={this.state.geocoding ? "Geocoding..." : "Geocode"}
          color="#ffffff"
        />

Alright, so that's part 1 all done. You can find the full code here.

Remember you'll need an Opencage API key to follow the tutorial.

In Part 2, we'll add a map view, use the GPS to get the current location and geocode it.