startLocationUpdatesAsync and BackgroundFetch not working in standalone app with Expo SDK 38

Please provide the following:

  1. SDK Version: 38
  2. Platforms(Android/iOS/web/all): iOS (standalone)

I have the following test code running in an app published to TestFlight (where the Firebase configs are appropriately populated and DataAgent refers to a working class that can update a Firebase database):

import * as Location from 'expo-location';
import * as BackgroundFetch from 'expo-background-fetch';
import Constants from "expo-constants";
import DataAgent from "./data/DataAgent";
import * as firebase from "firebase";

const LOCATION_TASK_NAME = 'background-location-task';
const FETCH_TASK_NAME = "fetch-task"

export default class Component extends React.Component {
    onPress = async () => {
        await BackgroundFetch.registerTaskAsync(FETCH_TASK_NAME);

        const { status } = await Location.requestPermissionsAsync();
        console.log(status);
        if (status === 'granted') {
            await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
                accuracy: Location.Accuracy.Highest,
            });
        }
        alert("done");
    };

    render() {
        return (
            <TouchableOpacity onPress={this.onPress} style={{flex: 1, height: "100%", width: "100%", justifyContent: 'center', alignItems: 'center'}}>
                <Text>Enable background location</Text>
            </TouchableOpacity>
        );
    }
}

TaskManager.defineTask(FETCH_TASK_NAME, () => {
    setTimeout(() => {
        return BackgroundFetch.Result.NewData;
    }, 28 * 1000)

    try {
        if (!firebase.apps.length) {
            firebase.initializeApp({
                apiKey: "",
                authDomain: "",
                databaseURL: "",
                storageBucket: ""
            });
        }
        const dataAgent = new DataAgent();

        if (Constants.isHeadless) {
            dataAgent.logBackgroundLocation({
                message: "FETCHED",
                isHeadless: Constants.isHeadless,
                isProduction: !__DEV__
            });

            Location.getCurrentPositionAsync({}).then((location) => {
                let lat = location.coords.latitude;
                let long = location.coords.longitude;
                let newUserLocation = {
                    latitude: lat,
                    longitude: long
                }
                console.log("App: updated current location")
                dataAgent.logBackgroundLocation({
                    message: "LOCATION",
                    location: newUserLocation,
                    isHeadless: Constants.isHeadless,
                    isProduction: !__DEV__
                });
            }).catch((err) => {
                dataAgent.logBackgroundLocation({
                    message: "ERROR: " + err,
                    isHeadless: Constants.isHeadless,
                    isProduction: !__DEV__
                });
            }).finally(() => {

            })
        }
    } catch (error) {

    }
});

TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
    if (!firebase.apps.length) {
        firebase.initializeApp({
            apiKey: "",
            authDomain: "",
            databaseURL: "",
            storageBucket: ""
        });
    }
    const dataAgent = new DataAgent();

    if (Constants.isHeadless) {
        dataAgent.logBackgroundLocation({
            message: "Initial",
            isHeadless: Constants.isHeadless,
            isProduction: !__DEV__
        });
        if (error) {
            dataAgent.logBackgroundLocation({
                message: "Error: " + error,
                isHeadless: Constants.isHeadless,
                isProduction: !__DEV__
            });
            // Error occurred - check `error.message` for more details.
            console.log(error);
            return;
        }
        if (data) {
            const { locations } = data;
            console.log(locations);
            dataAgent.logBackgroundLocation({
                message: "Locations",
                locations: [locations[0].coords.latitude, locations[0].coords.longitude],
                isHeadless: Constants.isHeadless,
                isProduction: !__DEV__
            });
            // do something with the locations captured in the background
        }
    }
});

Without the restriction of Constants.isHeadless, this code would successfully update my database when the app was in the foreground or background (not terminated). However, the code did not work when the app was terminated (swiped up from the task manager), and after adding the restriction of Constants.isHeadless, it does not update the database at all. This leads me to think that the app is never being launched in headless mode. Here is my app.json:

{
  "expo": {
    "name": "something",
    "slug": "something",
    "version": "1.0.1",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": false,
      "infoPlist": {
        "UIBackgroundModes": [
          "location",
          "fetch"
        ],
        "NSLocationWhenInUseUsageDescription": "something",
        "NSLocationAlwaysAndWhenInUseUsageDescription": "something",
        "NSLocationAlwaysUsageDescription": "something"
      },
      "bundleIdentifier": "something"
    },
    "android": {
      "package": "something",
      "permissions": [
        "ACCESS_COARSE_LOCATION",
        "ACCESS_FINE_LOCATION"
      ]
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

Has anyone had success in using background location tracking (or background fetch) in a terminated state on a standalone app with Expo SDK 38? Should I try downgrading my Expo SDK version or ejecting?

Any help would be greatly appreciated. Thank you for your time.

hey @ericthestein! I think this forum reply may help to illuminate how the TaskManager is working behind the scenes, especially with regards to location updates. In short, a significant change in location is required to spin it up when the app is terminated, so unless your position is changing by >500m, it may not spin up the app

1 Like

@charliecruzan

Thanks for the reply. I had some luck in triggering location updates (indicated by a blue indicator on my iOS simulator) after simulating a move of more than 500m, but my code doesn’t seem to be getting executed. Does the code in TaskManager.defineTask need to be completely asynchronous (i.e. no promises or awaits)? If so, that may explain why my firebase functions are not finishing their execution.
As a side question, is it possible to hide the aforementioned blue indicator when my app is using location services? Thank you.

Does the code in TaskManager.defineTask need to be completely asynchronous

No, I don’t believe so

is it possible to hide the aforementioned blue indicator when my app is using location services?

Can’t hide this, as people want more knowledge/awareness of what apps are using their data and how, it’s an OS level feature to alert whenever an app is grabbing your lcoation

@charliecruzan

Got it. The issue for me had to do with not being able to access data in React Native AsyncStorage while in headless mode, and then having the task crash because I was trying to loop through those undefined values before logging to my database. I took an alternative route, however, and everything worked out! Thanks for the insights.

Also, by blue indicator, I am referring to IMG_2445
not location-services-arrow . Is it possible to only show the latter and hide the former with Expo, perhaps with less frequent location updates? Thanks again!