loading...

Building a Coffee Map with React Native

iwilsonq profile image Ian Wilson ・6 min read

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

Then run

exp init espressopedia

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

and to build the project

exp start

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',
  },
});

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%'
  }
}

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>
    );
  }
}

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() { /* ... */ }
}

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

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
};

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>
    );
  }
}

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.

Posted on Feb 15 '18 by:

iwilsonq profile

Ian Wilson

@iwilsonq

I mostly just code or run. Aside from programming I'm generally training for a marathon.

Discussion

markdown guide
 

Why is it that hotshot web developers are willing to learn Javascript, Python, Go, Fortran and Basic but for some reason draws the line learning Swift or Objective-C? As a native mobile developer I honestly don't understand this mindset.

 

In a way it isn't just as simple as learning Swift/ObjC it is about learning the framework whether you're Kotlin on Android or Swift on iOS the real things you learn are about Android and iOS, right?

I think it is easy to understand that someone would want to take existing knowledge of React (React is to JS & HTML as iOS is to Swift) and get running really quickly.

These days given that for 90%+ of apps you'll see on either App store you can develop a very similar App in a tech you already know like HTML, CSS & JS especially with React, I think the motivation is really easy to understand.

Another point I would make is that JS especially is far more ubiquitous than something like Swift or Kotlin. ObjC is different enough to C/C++ and variants to be also quite niche.

Why would you learn another niche language when you can use what you already know to build Apps for 90% of use cases. As a web developer I honestly don't understand this mindset :P

 

Ah yeah I put Fortran and Basic there intentionally so it sounds more ridiculous. This is not to say that there is such a stereotype, just light humor.

 

Love your writing style, Ian! Would be super curious what you think of the revamped Mapbox Maps SDK for React Native if you have another mapping project in the works, github.com/mapbox/react-native-map...

 

Thank you Erin, glad you enjoyed :)

Funny enough I was in SF the other day and I met my brother after work. We wanted to find all of the restaurants downtown that had happy hour near us (it was about 6:30 so not all of them did!). I remember trying to yelp that but they didn't give those specific hours for happy hour when you tap into a business. There seems to be an idea for an app if its not already out there.

That aside, I'll be sure to check out mapbox again if I do embark on another map project %-)

 

Lol definitely, let's build it!!! Love the idea.

 

Thanks a lot for the detailed tutorial. Extremely helpful. I was able to write a store locator app and published it on instamobile.io, by following much of your article. Cheers!

 

Wow, I just saw this. Congratulations! :)

 

This tutorial does not work, I could not get past the first step.

Can't read JSON file: C:\Users\abe\Desktop\projects\firstapp\onlineTutorial\package.json
[18:08:37] └─ Cause: Error: ENOENT: no such file or directory, open 'C:\Users\abe\Desktop\projects\firstapp\onlineTutorial\package.json'

and:
18:11:08.Exp.downloadTemplateApp is not a function

Ive been searching high and low for the last week for some tutorial or a working map library in react native, have yet to find one that actually compiles or works.

 

I think I may revisit this tutorial as it is rather old. React Native and her libraries have developed a lot and its quite possible that API changes may have broken some of the steps here.

Thank you for letting me know

 

What will break if you eject expo so that this function could be integrated into other non expo projects?