Building a Coffee Map with React Native

Ian Wilson - Feb 15 '18 - - Dev Community

The young web developer knows the web. They have spent countless hours slinging divs and casting margins. They have hammered out countless to-do applications in JavaScript, Python, Golang, Fortran, Basic... you name it!

But now, this hotshot developer wants to conquer a less familiar territory. They want to displace their teams' gang of mobile developers; all of them, by using the hip new framework, React Native. Thinking it'll be practically like writing a simple web application, they install the React Native CLI and scaffold an empty project.

It starts off just fine. They find out that instead of divs, they must create Views. In order to create text, they must use the built in Text component. Instead of CSS, they must use inline JavaScript styles. In order to create a nice layout, they require some knowledge of flexbox.

But then they want to wield more powerful features like geolocation, audio input, or push notifications. They find that in order to enable these features, they must open up XCode and edit some fairly verbose configuration files, change the plist, and create headers and implementations in Objective-C. They find that maybe they should stick to the web.

Enter Expo

Fortunately, the beautiful team over at Expo has created a pretty powerful SDK that greatly improves the React Native developer experience. They have made it such that when you create an application with Expo, you will probably never have to crack open XCode or edit any platform specific configuration files.

If you're familiar with create-react-app for bootstrapping a React web application, Expo works in pretty much the same way. You run exp init <project-name> from the command line and then just enter the project directory and run it with exp start. Expo provides you with a QR code that you can use to view your project right on your device. You could also just run the simulator using exp ios or exp android. The simulator is a little bit faster between saves but doesn't have quite the performance as the real device.

Espressopedia

Its like expedia for coffee. Or something like that. From a high level standpoint, the app will go like this:

  • we will have a map view with the user's location in center
  • on the map will be a set of markers for the locations of coffee & tea shops nearby

We will use the Yelp API for getting the list of coffee places. Their API is pretty straightforward to setup and use, just head over to Yelp and sign up then create an app.

Creating a new project

Lets get coding. Start by installing the expo cli.

npm install -g exp
Enter fullscreen mode Exit fullscreen mode

Then run

exp init espressopedia
Enter fullscreen mode Exit fullscreen mode

It'll ask you whether you want to set up a blank template project or one with some starter files like a tab navigator. I chose the blank project because we wont need any tab navigation.

Now I'm going to test the app in the iOS simulator. You can use your own device as well but then its up to you to download the expo client and set it up. To run the simulator:

exp ios

# or for Android

exp android
Enter fullscreen mode Exit fullscreen mode

and to build the project

exp start
Enter fullscreen mode Exit fullscreen mode

Now when you open up your root directory you will find the blank template App.js file.

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

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Enter fullscreen mode Exit fullscreen mode

If you're a React veteran, this file shouldn't look too intimidating. Note the usage of View and Text tags. This file uses StyleSheet but we could've defined styles as a plain object as well.

Building the Map

The first expo API we'll explore is the MapView component.

// app/components/Map.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { MapView } from 'expo';

const Marker = MapView.Marker;

export default class Map extends Component {
  renderMarkers() {
    return this.props.places.map((place, i) => (
      <Marker 
        key={i}
        title={place.name}
        coordinate={place.coords}
      />
    ));
  }

  render() {
    const { region } = this.props

    return (
      <MapView
      style={styles.container}
      region={region}
      showsUserLocation
      showsMyLocationButton
      >
        {this.renderMarkers()}
      </MapView>
    );
  }
}

const styles = {
  container: {
    width: '100%',
    height: '80%'
  }
}
Enter fullscreen mode Exit fullscreen mode

This Map component is a wrapper for Expo's MapView component. By making electing to wrap the built-in component we can decorate our map with functionality through lifecycle methods and application-specific methods, such as rendering the markers. Here, it is not implemented specific to our use case of finding coffee shops -- that decision is made in the App.js component that renders it.

// App.js
import React from 'react';
import { Text, SafeAreaView } from 'react-native';
import Map from './app/components/Map'

// A placeholder until we get our own location
const region = {
  latitude: 37.321996988,
  longitude: -122.0325472123455,
  latitudeDelta: 0.0922,
  longitudeDelta: 0.0421
}

export default class App extends React.Component {
  state = {
    region: null
    coffeeShops: []
  }

  render() {
    return (
      <SafeAreaView style={styles.container}>
        <Map
          region={region}
          places={this.state.coffeeShops}
        />
      </SafeAreaView>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we pass down an initial region object which should put your map somewhere around the city of Cupertino. We'll replace this when we get user location to center our map view. We also use SafeAreaView for the top level component. This allows our content to look good even with the iPhone X's whacky screen region.

Getting User Location

To get the user location, we can use the Location and Permissions modules within expo. Add this to App.js

// App.js
/* ... */
import { Location, Permissions } from 'expo'

const deltas = {
  latitudeDelta: 0.0922,
  longitudeDelta: 0.0421
};

export default App extends Component {
  state = {
    region: null,
    coffeeShops: []
  };

  componentWillMount() {
    this.getLocationAsync();
  }

  getLocationAsync = async () => {
    let { status } = await Permissions.askAsync(Permissions.LOCATION);
    if (status !== 'granted') {
      this.setState({
        errorMessage: 'Permission to access location was denied'
      });
    }

    let location = await Location.getCurrentPositionAsync({});
    const region = {
      latitude: location.coords.latitude,
      longitude: location.coords.longitude,
      ...deltas
    };
    await this.setState({ region });
  }

    render() { /* ... */ }
}

Enter fullscreen mode Exit fullscreen mode

Here we make sure to get user's permission to use geolocation as our App is mounting. If they refuse, we set an errorMessage in state and have the option of display that rather than the map. Once permission is granted, we can call getCurrentPositionAsync which returns a location object that is a little more complex than we need, so we massage it getting only the properties we want, namely latitude and longitude (and the deltas so our map knows how much to zoom).

Fetching data

To get our coffee shop data, we'll query the Yelp API. Its pretty easy to setup an app on Yelp, just log in and go to Manage App. Here you'll get an API key which you can use to consume their API.

Just like on web, we can leverage the axios library to perform HTTP requests. Go ahead and run

npm install --save axios
Enter fullscreen mode Exit fullscreen mode

Now for the sake of modularity, I will add a new folder called services inside the app directory, and inside this folder, a file called yelp.js. Here we defined how our application will interface with Yelp's API.

// app/services/yelp.js
import axios from 'axios';

const YELP_API_KEY = '<YOUR_API_KEY>';

const api = axios.create({
  baseURL: 'https://api.yelp.com/v3',
  headers: {
    Authorization: `Bearer ${YELP_API_KEY}`
  }
});

const getCoffeeShops = userLocation => {
  return api
    .get('/businesses/search', {
      params: {
        limit: 10,
        categories: 'coffee,coffeeroasteries,coffeeshops',
        ...userLocation
      }
    })
    .then(res =>
      res.data.businesses.map(business => {
        return {
          name: business.name,
          coords: business.coordinates
        };
      })
    )
    .catch(error => console.error(error));
};

export default {
  getCoffeeShops
};
Enter fullscreen mode Exit fullscreen mode

This service works by creating an http client with axios.create and passing in the baseURL and the Authorization header. We can then use it to query the Yelp api by sending a GET request to https://api.yelp.com/v3/businesses/search with query parameters. Axios makes this easier by allowing us to set the parameters as an object in its argument list. After that, the only part of this getCoffeeShops method that makes it unique to our app is where we specify categories in the request. We could change that to "vegan" or "burgers" and it would change the results of our map completely. Well mostly.

Now let us consume this service inside App.js, start by importing YelpService.

// App.js
/* ... */
import YelpService from './app/services/yelp'

export default App extends Component {

  /* ... */

  getCoffeeShops = async () => {
    const { latitude, longitude } = this.state.region;
    const userLocation = { latitude, longitude };
    const coffeeShops = await YelpService.getCoffeeShops(userLocation);
    this.setState({ coffeeShops });
  };

  getLocationAsync = async () => {

    /* ... */

    // Add this line!
    await this.getCoffeeShops();
  }

  render() {
    const { region, coffeeShops } = this.state;
    return (
      <SafeAreaView style={styles.container}>
        <Map region={region} places={coffeeShops} />
      </SafeAreaView>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we're in business! You should be able to see a map with markers on it, as well as your location. Unless you're on a simulator. Then you'll see that you're in San Francisco somewhere. I wonder if that is where the Expo team works?

Final

I hope you got a kick out of this article somehow, hopefully it'll inspire you to make something even cooler. During my prep for this article I created a similar app with a few more bells and whistles in that it even has filter buttons. One of the filters is "Starbucks", you know in case you couldn't find them all. If you're interested, you can see that code here.

Since this is my first post, I'd appreciate comments, suggestions, or critiques. That will fire up engagement and make sure that future posts are even better.

Until next time.

. . . . . . . . . . . . . . . . .
Terabox Video Player