Can you track background geolocation with React Native?
Asked Answered
C

5

13

Problem

I'd like to be able to track a users location even when the app is no longer in the foreground (e.g. The user has switch to another app or switched to the home screen and locked their phone).

The use case would be a user tracking a run. They could open the app and press 'start' at the beginning of their run, then switch or minimise the app (press the home button) and lock the screen. At the end of the run they could bring the app into the foreground and press 'stop' and the app would tell them distance travelled on the run.

Question

Is tracking background geolocation possible on both iOS and Android using pure react native?

The react native docs on geolocation (https://facebook.github.io/react-native/docs/geolocation) are not very clear or detailed. The documented linked above eludes to background geolocation on iOS (without being fully clear) but does not mention Android.

Would it be best that I use Expo?

Cryptogam answered 21/2, 2019 at 16:31 Comment(0)
J
15

UPDATE 2019 EXPO 33.0.0:

Expo first deprecated it for their SDK 32.0.0 to meet app store guidelines but then reopened it in SDK 33.0.0.

Since, they have made it super easy to be able to implement background location. Use this code snippet that I used to make background geolocation work.

import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';

const LOCATION_TASK_NAME = 'background-location-task';

export default class Component extends React.Component {
  onPress = async () => {
    await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
      accuracy: Location.Accuracy.Balanced,
      timeInterval: 5000,
    });
  };

  render() {
    return (
      <TouchableOpacity onPress={this.onPress} style={{marginTop: 100}}>
        <Text>Enable background location</Text>
      </TouchableOpacity>
    );
  }
}

TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
  if (error) {
    alert(error)
    // Error occurred - check `error.message` for more details.
    return;
  }
  if (data) {
    const { locations } = data; 
    alert(JSON.stringify(locations); //will show you the location object
    //lat is locations[0].coords.latitude & long is locations[0].coords.longitude
    // do something with the locations captured in the background, possibly post to your server with axios or fetch API 
  }
});

The code works like a charm. One thing to note is that you cannot use geolocation in the Expo App. However, you can use it in your standalone build. Consequently, if you want to use background geolocation you have to use this code and then do expo build:ios and upload to the appstore in order to be able to get a users background location.

Additionally, note that you must include

"UIBackgroundModes":[
          "location",
          "fetch"
        ]

In the info.plist section of your app.json file.

Jade answered 9/7, 2019 at 0:55 Comment(4)
What do you mean by you can use it in your standalone build . TaskManager is not available for bare react native apps, the docs sayBaseline
expo not tracking location in background on androidEkaterinburg
How can i track location when app is killed or in background Note:- I want to achive this with CLI not with expoStadiometer
this doesn't really explain how to get the location in app state, see my answer below if you need the location to be accessible in the app state.Factorage
B
4

The Expo Team release a new feature in SDK 32 that allow you tracking in background the location.

https://expo.canny.io/feature-requests/p/background-location-tracking

Billi answered 21/2, 2019 at 19:59 Comment(0)
T
3

Yes is possible, but not using Expo, there are two modules that I've seen:

This is a comercial one, you have to buy a license https://github.com/transistorsoft/react-native-background-geolocation

And this https://github.com/mauron85/react-native-background-geolocation

Teran answered 21/2, 2019 at 16:52 Comment(5)
I have seen those two modules. I'm concerned about the stability of the second one, unsure if I should be? Also, Expo now say they support background geolocation as of Jan 2019.Cryptogam
I think it's you best bet to start, other option is doing yourself the code in Swift/Java/Kotlin and then glueing it to RNTeran
@Teran Thanks for sharing . transistorsoft is a paid Library , where as mauron85/react-native-background-geolocation is an open source , in my case mauron85 is better .Hike
as of today , which one is better for iOS ?Spatterdash
O Google deixa bem explicito que mesmo com intervalos menores definidos, as atualizações da geolocalização em Background não passam de algumas vezes por hora.Daugava
F
2

The most popular RN geolocation library is https://github.com/react-native-geolocation/react-native-geolocation, and it supports this quite easily. I prefer this library over others because it automatically handles asking for permissions and such, and seems to have the simplest API.

Just do this:

Geolocation.watchPosition((position)=>{
    const {latitude, longitude} = position.coords;
    // Do something.
})

This requires no additional setup other than including the background modes fetch and location, and also the appropriate usage descriptions.

I find this more usable than Expo's API because it doesn't require any weird top level code and also doesn't require me to do anything other than create a watch position handler, which is really nice.

EDIT 2023!:

These days I would highly recommend using Expo's library instead of any of the other community libraries (mainly because our app started crashing when android got an OS update b/c of the lib I was using).

In fact, if you have to choose between expo and non expo library, always choose the expo library if only for the stability. Setting up expo's background location watching isn't super well documented but here's what I did to get it working in our app:

import { useEffect, useRef } from "react";
import * as Location from "expo-location";
import { LatLng } from "react-native-maps";
import * as TaskManager from "expo-task-manager";
import { LocationObject } from "expo-location";
import { v4 } from "uuid";
type Callback = (coords: LatLng) => void;
const BACKGROUND_TASK_NAME = "background";

const executor: (body: TaskManager.TaskManagerTaskBody<object>) => void = (
  body
) => {
  const data = body.data as unknown as { locations: LocationObject[] };
  const l = data?.locations[0];
  if (!l) return;

  for (const callback of Object.values(locationCallbacks)) {
    callback({
      latitude: l.coords.latitude,
      longitude: l.coords.longitude,
    });
  }
};

TaskManager.defineTask(BACKGROUND_TASK_NAME, executor);

const locationCallbacks: { [key: string]: Callback } = {};
const hasStartedBackgroundTaskRef = {
  hasStarted: false,
};

function startBackgroundTaskIfNecessary() {
  if (hasStartedBackgroundTaskRef.hasStarted) return;
  Location.startLocationUpdatesAsync(BACKGROUND_TASK_NAME, {
    accuracy: Location.Accuracy.Balanced,
  }).catch((e) => {
    hasStartedBackgroundTaskRef.hasStarted = false;
  });
  hasStartedBackgroundTaskRef.hasStarted = true;
}

function addLocationCallback(callback: Callback) {
  const id = v4() as string;
  locationCallbacks[id] = callback;
  return {
    remove: () => {
      delete locationCallbacks[id];
    },
  };
}

export default function useLocationChangeListener(
  callback: Callback | null,
  active: boolean = true
) {
  const callbackRef = useRef<null | Callback>(callback);
  callbackRef.current = callback;

  useEffect(() => {
    if (!active) return;
    if (!callback) return;
    Location.getLastKnownPositionAsync().then((l) => {
      if (l)
        callback({
          latitude: l.coords.latitude,
          longitude: l.coords.longitude,
        });
    });
    startBackgroundTaskIfNecessary();
    const watch = Location.watchPositionAsync({}, (location) => {
      callback({
        latitude: location.coords.latitude,
        longitude: location.coords.longitude,
      });
    });
    const subscription = addLocationCallback(callback);
    return () => {
      subscription.remove();
      watch.then((e) => {
        e.remove();
      });
    };
  }, [callback, active]);

  useEffect(() => {
    if (__DEV__) {
      addLocationCallback((coords) => {
        console.log("Location changed to ");
        console.log(coords);
      });
    }
  }, []);
}

You need to ask for background location permissions before this, BTW. Follow expos guide.

It's pretty risky trusting community libraries for stuff like this because of the fact that breaking android OS updates can happen at any moment and with open source maintainers they may or may not stay on top of it (you can more or less trust expo too, though)

Factorage answered 25/8, 2021 at 14:44 Comment(6)
it is not working in background.Kiwanis
In my experience (RN 0.67, iOS 15.4, Xcode 13.3) this package is working in the background, whether the user minimizes it, switches to another app, or even locks the device. The watchPosition() method nicely encapsulates almost everything required to continuously get position updates.Lysenko
Unfortunately, it didn't work for me. Maybe my app needs some other permission. I'm trying to figure out.Individualize
Can I be able to save the location object to firebase while the app is in background?Wager
Does this also work in a non-Expo React Native app?Mcmurray
Does this also work in a non-Expo React Native app ?Percyperdido
H
0

Webkit is currently evaluating a Javascript-only solution. You can add your voice here

For a fully documented proof-of-concept example please see Brotkrumen.

Hammons answered 8/7, 2019 at 3:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.