y_qweq
Last Updated: November 28, 2018
·
31
· mirabektimirova

How to build a Location-based App using React Native

cover

What good can happen when we tap Allow on the pop-up that asks to access our location? Some apps provide better experience, like Facebook suggesting events nearby. Others — can’t work properly without knowing device location, like Uber or Google Maps.

These location-based apps use device location to enable and control some features. From wok delivery to Find My iPhone, location-based apps help us with our everyday tasks just by knowing where we are.

img1
Location can be either the primary function, like in Tinder; or auxiliary, like in Instagram: when uploading a photo, Instagram will suggest you a place so you can tag your location. Whether it’s the main function or not, location does improve the user experience.

In this article, I’ll tell you about the main tech components of location-based apps, and you’ll learn how to develop one using React Native. First, I’ll briefly describe React Native and compare it to native app development. Then I’ll share approaches to gathering and displaying the location in an app, and finally, in a short passage I’ll describe some of the design challenges and how React Native copes with them.

Tools For Location-Based Application Development: React Native

In this part, I will briefly describe the React Native framework, its pros & cons, and why it’s great for building location-based apps. React Native is an open source JavaScript framework that allows developers create cross-platform apps with their native behavior.

What is behavior in this description? Let me explain. iOS and Android are different — their interface is different, their animation is different, lots of things are different. Having the same app for iOS and Android would require developing two separate apps. It used to be a laborious process, but using React Native, developers write one code, and it works correctly on both platforms.

This allows businesses to offer their app to both iOS and Android users which means a bigger market share. That’s why many companies prefer React Native — they can’t afford developing two separate apps or are confident about whether their users have iOS or Android. And considering that the cross-platform market is likely to grow to $80 billion by 2020, it seems a rational choice for startups.

Now, I’ll explain the pros and cons of React Native in terms of developing location-based apps.

React Native Pros

  • Cross-platform. Rather than writing separate code for each system (iOS and Android), you build one to operate them both. Neither do you design different UI and UX.
  • High performance. React Native uses native controls and modules. The code interacts with the corresponding native iOS and Android components and renders the code to native APIs. Native API is the focus — by using a separate from UI thread, it increases the performance of the app.
  • Open source. The React Native community grows every day, and so does the number of open-source components. This allows for sharing the experience among the community members, improving the framework, and finding the solutions to existing bugs. All this combined accelerates the development process.
  • It saves money. The three previous points conclude into a considerable advantage — React Native saves your money. It’s faster than building two separate apps and so it takes less time for testing and releasing an MVP. However, there are cases when you might not want to use React Native.

They include:

  • You don’t need a cross-platform app. If you know that your audience prefers a particular platform, I suggest you opt for native development. Firstly, the app will be tailored to match the specifics of the OS, and secondly, you’ll be able to use platform-specific features, like ARKit for iOS.
  • You need more APIs than React Native offers. The one thing I particularly dislike is that React Native has a limited number of supported native APIs. There are enough to build a location-based app, though. In case you need others — you can bridge them using the native code. img2

How to gather and display user location

This part is about gathering and displaying the location data. Depending on the specifics of your app, you will opt for a particular way.

Gathering location data

I single out three ways to gather device location.

Note: This is a generic overview for you to understand the cases when to opt for each case and the differences between them.

Using React Native API

There’s a native JavaScript API that identifies the location of a device. It’s easy to install and use, but there’s a ‘but’ — it neither works on the background nor shows the location provider (3G, Wi-Fi, GPS).

react-native-background-geolocation

It’s a package that determines the location of a device from 0 to 1000 meters (0.6 miles). It takes more battery energy, but on the other hand, it’s up to you to configure how often to track the location. The package also integrates with SQLite — you can store the recorded location data and sync it to your database via HTTP.

import { NativeModules, DeviceEventEmitter, PermissionsAndroid } from 'react-native'  
import Config from 'react-native-config'  
import get from 'lodash/get'  
const { GeoLocation } = NativeModules
class BackgroundGeoLocation {  
  constructor(token, user_id) {
    this.state = null
  }
  start(dispatch, nextState) {
    this.dispatch = dispatch
    const token = get(nextState, 'session.data.token')
    const user_id = get(nextState, 'user.data.user_id')
    const id = get(nextState, 'user.data.id')
    this.state = {
      user_id,
      token,
    }
    return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION)
      .then(is_granted => is_granted === PermissionsAndroid.RESULTS.GRANTED
        ? is_granted
        : PermissionsAndroid.requestMultiple([
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
        ])
      )
      .then(_ => PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION))
      .then(is_granted => is_granted === PermissionsAndroid.RESULTS.GRANTED ? true : new Error())
      .then(_ => setTimeout(() => GeoLocation.startService(token, user_id, id, `${Config.API_URL}/live/car-tracking/gps-pos/`), 300))
      .catch(e => console.log(e))
  }
  stop() {
    return GeoLocation.stopService()
      .then(_ => console.log(_))
  }
  handleLocationChange(geo) {
    console.log(geo)
  }
}
export default BackgroundGeoLocation

Being a package, it requires regular maintenance and updates, but there’s a support channel on GitHub from the creator of the package.

Note: Package price: iOS — free; Android — $300 for one app.

Combo: Bridge native code to JavaScript API
The main issue with the first approach (to use native JavaScript API) can be solved by adding native code that will start a foreground service in a separate thread.

package com.djangostars.azyan;
import android.app.Notification;  
import android.app.NotificationChannel;  
import android.app.NotificationManager;  
import android.content.Context;  
import android.content.Intent;  
import android.content.pm.PackageManager;  
import android.location.Criteria;  
import android.location.Location;  
import android.location.LocationManager;  
import android.os.Build;  
import android.support.v4.content.ContextCompat;
import com.facebook.react.bridge.Arguments;  
import com.facebook.react.bridge.Promise;  
import com.facebook.react.bridge.ReactApplicationContext;  
import com.facebook.react.bridge.ReactContextBaseJavaModule;  
import com.facebook.react.bridge.ReactMethod;  
import com.facebook.react.bridge.WritableMap;  
import com.facebook.react.bridge.WritableNativeMap;

/**
 * Created by AGulchenko on 5/7/18.
 */
public class GeoLocationModule extends ReactContextBaseJavaModule {  
    public static final String CHANNEL_ID = "ExampleService_Channel";
    public GeoLocationModule(ReactApplicationContext reactContext) {
        super(reactContext);
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,"testName", NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager manager = reactContext.getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }
    @Override
    public String getName() {
        return "GeoLocation";
    }
    @ReactMethod
    public void startService(String token, String user_id, String id, String url_string, Promise promise) {
        WritableMap result = Arguments.createMap();
        result.putString("ststus", "success");
        try {
            Intent serviceIntent = new Intent(getReactApplicationContext(), GeoLocationService.class);
            serviceIntent.putExtra("token", token);
            serviceIntent.putExtra("user_id", user_id);
            serviceIntent.putExtra("id", id);
            serviceIntent.putExtra("url_string", url_string);
            getReactApplicationContext().startService(serviceIntent);
            promise.resolve(result);
        } catch (Exception e) {
            e.printStackTrace();
            promise.reject("rrrrr",e);
            return;
        }
    }
    @ReactMethod
    public void stopService(Promise promise) {
        String result = "Success";
        try {
            Intent serviceIntent = new Intent(getReactApplicationContext(), GeoLocationService.class);
            getReactApplicationContext().stopService(serviceIntent);
        } catch (Exception e) {
            promise.reject(e);
            return;
        }
        promise.resolve(result);
    }
    @ReactMethod
    public void getLocation( Promise promise) {
        WritableMap res = Arguments.createMap();
        try {
            LocationManager locationManager = null;
            locationManager = (LocationManager) this.getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
            int permissionCheck = ContextCompat.checkSelfPermission(this.getReactApplicationContext(),
                    android.Manifest.permission.ACCESS_FINE_LOCATION);
            if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
                Criteria criteria = new Criteria();
                String bestProvider = locationManager.getBestProvider(criteria, false);
                Location location = locationManager.getLastKnownLocation(bestProvider);
                if(location != null) {
                    res.putDouble("latitude", location.getLatitude());
                    res.putDouble("longitude", location.getLongitude());
                    promise.resolve(res);
                }
            }
        } catch (Exception e) {
            promise.reject(e);
            return;
        }
    }
}

Getting permission to access the location data sometimes causes troubles, but the troubles are bearable. React Native solves the problem without too much fuss.

Different systems ask for the permission to access the location data on different stages: iOS requests the permission first time you open an app; Android — upon the download. It could cause trouble if we were using the native code, however, React Native simplifies this process using the check access to location data module. It allows access to location data without triggering the permission alert.

Displaying Location

The location data isn’t always precise. You must have seen something like this:
img3

Here’s why it may happen: the device collects the location data from three sources: Wi-Fi, cellular and GPS, the latter being the least accurate. Our devices are in the constant state of checking if there’s good Internet connection. If there’s none, the device will enable GPS. And if there’s a quick leap from 4G to GPS, the device is ‘lost’.

To solve this problem, I recommend Fused Location Client by Google. It allows you to set the time and distance at which the location data is updated: for instance, update the data every 50 meters and every 10 seconds. You will avoid the noisy data as this API matches all device locations to roads and sidewalk. However, if the device is far from either, it won’t be effective.

A Few Words About Design

In this short part, I will tell you about obstacles that may arise with building location-based apps and how to solve them with React native components.

React Native allows for simple ways of displaying maps. It UI components for Material Design simplify the job for engineers. We would use Material Design to create a Google Maps wrapper, and then React Native would adjust them to the specific features of each platform.

Infinite List is a React Native feature that makes an endless list of search results. In Uber, if you start typing an address, you will get all the destinations starting with what you’ve entered. So try 3rd Ave, it will show all 3rd Aves around. Such lists aren’t really endless — they just load more results as we scroll down through them. Flat List — another build-in UI component includes the fixed header, footer, and delimiters to the search. If you were to create such result lists from scratch, it would take you more time than building a complete React native app.

<FlatList  
        data={get(props, 'order.data.items', [])}
        renderItem={listItem}
        keyExtractor={({ id }) => id}
        style={style.list}
      />
function listItem({ item: { quantity, name } }) {  
  return (
    <View style={style.main}>
      <Text style={[style.count, style.listItemText]}>{quantity}</Text>
      <Text style={[style.listItemText, style.name]}>{name}</Text>
    </View>
  )
}

Bottom Line

React Native may be the right choice if you are going to build a location-based application. If most of the described advantages are true for you, go do deeper research on the technology and its capabilities.

I now encourage you to pay more attention to the apps you use. You’ll be surprised to find out that most of them ask to allow access to your location. For many companies, it’s crucial to know user location to provide quality and more user-oriented service. Why don’t you try?

This article about building a location-based app with react native is written by Julia Korsun. Originally published on Django Stars blog.