react-native-background-geolocation icon indicating copy to clipboard operation
react-native-background-geolocation copied to clipboard

[Help Wanted]: BackgroundGeolocation Keeps Sending Location Updates Even After Calling stop()

Open shubhlumber opened this issue 8 months ago • 17 comments

Required Reading

  • [x] Confirmed

Plugin Version

^4.16.5

Mobile operating-system(s)

  • [x] iOS
  • [x] Android

What do you require assistance about?

We are using react-native-background-geolocation to track user location after they grant access. We are using the URL method to automatically send location updates to a server.

The issue occurs when a user is clocked out from a different device. In this case, we want to stop tracking their location immediately and stop hitting ping api.

We attempted to achieve this using the onHttp event as follows:

Observed Behavior: The API is still receiving location updates even after stop() is invoked.

[Optional] Plugin Code and/or Config

useEffect(() => {
    if (Platform.OS !== 'web' && token?.authToken) {
      const onProvider: Subscription = BackgroundGeolocation.onProviderChange((event) => {
        switch (event.status) {
          case BackgroundGeolocation.AUTHORIZATION_STATUS_DENIED:
            break;
          case BackgroundGeolocation.AUTHORIZATION_STATUS_ALWAYS:
            break;
          case BackgroundGeolocation.AUTHORIZATION_STATUS_WHEN_IN_USE:
            break;
        }
      });

      const onLocation: Subscription = BackgroundGeolocation.onLocation(
        (location) => {
          setLongitude(location?.coords.longitude);
          setLatitude(location?.coords.latitude);
        },
        (error) => {
          console.log('[onLocation] ERROR: ', error);
        }
      );

      const http: Subscription = BackgroundGeolocation.onHttp((response) => {
        console.log('http', response);
        if (response.status !== 200) {
          BackgroundGeolocation.stop();
        }
      });

      const motionChange: Subscription = BackgroundGeolocation.onMotionChange((event) => {});
      /// 2. ready the plugin.
      BackgroundGeolocation.ready({
        notification: {
          priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_DEFAULT,
          title: 'abc',
          text: 'We are tracking you',
        },
        isMoving: false,
        url: `${config.API_URL}/api/ping`,
        autoSync: false,
        maxRecordsToPersist: 1,
        method: 'POST',
        elasticityMultiplier: 6,
        distanceFilter: 15,
        disableLocationAuthorizationAlert: !getAllowAllLocationUdf,

        headers: {
          Authorization: 'Bearer ' + token.authToken,
          timezone: moment.tz.guess(),
        },
        showsBackgroundLocationIndicator: true,
        httpRootProperty: '.',
        locationTemplate:
          '{"latitude":<%= latitude %>,"longitude":<%= longitude %>,"time":"<%= timestamp %>"}',
        backgroundPermissionRationale: {
          title: 'Allow abc to use the location',
          message:
            'abc utilizes your GPS while clocked in for seamless collaboration among all staff.',
          positiveAction: 'Change to {backgroundPermissionOptionLabel}',
          negativeAction: 'Cancel',
        },
        desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
        stopTimeout: 1,
        debug: false,
        logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
        stopOnTerminate: false,
        startOnBoot: true,
      })
        .then((state) => {})
        .catch((err) => console.error(err));

      return () => {
        onProvider.remove();
        onLocation.remove();
        http.remove();
        motionChange.remove();
      };
    }
  }, [getAllowAllLocationUdf]);

  const delayedReset = () => {
    BackgroundGeolocation.reset({
      notification: {
        priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_DEFAULT,
        title: 'Lumber',
        text: 'Lumber is tracking you',
      },
      isMoving: true,
      url: `${config.API_URL}/api/ping`,
      autoSync: status === 'CLOCKED IN' ? true : false,
      maxRecordsToPersist: 1,
      method: 'POST',
      elasticityMultiplier: 6,
      distanceFilter: 15,
      disableLocationAuthorizationAlert: !getAllowAllLocationUdf,
      headers: {
        Authorization: 'Bearer ' + token.authToken,
        timezone: moment.tz.guess(),
      },
      showsBackgroundLocationIndicator: true,
      httpRootProperty: '.',
      locationTemplate:
        '{"latitude":<%= latitude %>,"longitude":<%= longitude %>,"time":"<%= timestamp %>"}',
      backgroundPermissionRationale: {
        title: 'Allow abc to use the location',
        message:
          'abc utilizes your GPS while clocked in for seamless collaboration among all staff.',
        positiveAction: 'Change to {backgroundPermissionOptionLabel}',
        negativeAction: 'Cancel',
      },
      desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
      stopTimeout: 1,
      debug: false,
      logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
      stopOnTerminate: false,
      startOnBoot: false,
    })
      .then((state) => {})
      .catch((err) => console.error(err));
  };
  useEffect(() => {
    if (Platform.OS !== 'web' && token?.authToken) {
      delayedReset();
    }
  }, [status, token, getAllowAllLocationUdf]);

[Optional] Relevant log output


shubhlumber avatar Mar 25 '25 10:03 shubhlumber

Calling .stop() stops everything. The plug-in does not continue recording locations and posting to your configured url after calling .stop()

christocracy avatar Mar 25 '25 11:03 christocracy

I added a console.log inside the onHttp event to debug the issue:

const http: Subscription = BackgroundGeolocation.onHttp((response) => {
  console.log('http', response);
  if (response.status !== 200) {
    console.log('Stopping BackgroundGeolocation...');
    BackgroundGeolocation.stop();
  }
});

However, when the API returns a 500 or 400 error, the if condition is never triggered. It seems like the onHttp event is not even receiving the response in such cases.

I want to confirm:

  1. Does the URL method automatically stop tracking when the API returns an error response (e.g., 4xx or 5xx)?
  2. Is there any internal mechanism that prevents onHttp from being triggered when the server returns an error?
  3. If onHttp is not fired for error responses, what is the recommended way to detect such failures and stop tracking?

shubhlumber avatar Mar 25 '25 19:03 shubhlumber

The way to debug this is by observing the plug-in logs.

See wiki “Debugging”.

christocracy avatar Mar 25 '25 19:03 christocracy

Hi Chris, Do you have any suggestions for trying a fake GPS on an iOS real device for debugging? That would be helpful.

shubhlumber avatar Mar 27 '25 16:03 shubhlumber

The iOS Simulator and XCode have built-in ability to simulate location.

Image

christocracy avatar Mar 27 '25 16:03 christocracy

For real devices, you can provide a custom GPX file and load it in the Launch Scheme. I have attached a custom GPX file that follows the iOS Simulator "Freeway Drive" route (rename the file CityDrive.gpx.txt -> CityDrive.gpx)

CityDrive.gpx.txt

Image

christocracy avatar Mar 27 '25 16:03 christocracy

Also google "iOS simulate location xcode": https://medium.com/@merlos/how-to-simulate-locations-in-xcode-b0f7f16e126d

christocracy avatar Mar 27 '25 16:03 christocracy

This function is working fine on Android, but while debugging on iOS, I noticed that the logs from this function are not being printed when the app is in the background. As a result, the location tracking is not stopping. How can I resolve this issue?

const http: Subscription = BackgroundGeolocation.onHttp((response) => {
  console.log('http', response);
  if (response.status !== 200) {
    console.log('Stopping BackgroundGeolocation...');
    BackgroundGeolocation.stop();
  }
});

shubhlumber avatar Mar 30 '25 12:03 shubhlumber

This issue is stale because it has been open for 30 days with no activity.

github-actions[bot] avatar Apr 30 '25 02:04 github-actions[bot]

When the app is in the background or has been killed from the foreground, we are still receiving location notifications. However, during this time, we are not tracking the user's location, so we don’t want users to feel like their location is being tracked unnecessarily. Is there a way to remove or suppress this notification in such cases?

  useEffect(() => {
    if (Platform.OS !== 'web' && token?.authToken) {
      const onProvider: Subscription = BackgroundGeolocation.onProviderChange((event) => {
        switch (event.status) {
          case BackgroundGeolocation.AUTHORIZATION_STATUS_DENIED:
            break;
          case BackgroundGeolocation.AUTHORIZATION_STATUS_ALWAYS:
            break;
          case BackgroundGeolocation.AUTHORIZATION_STATUS_WHEN_IN_USE:
            break;
        }
      });

      const onLocation: Subscription = BackgroundGeolocation.onLocation(
        (location) => {
          setLongitude(location?.coords.longitude);
          setLatitude(location?.coords.latitude);
        },
        (error) => {
          console.log('[onLocation] ERROR: ', error);
        }
      );

      const http: Subscription = BackgroundGeolocation.onHttp(async (response) => {
        console.log('http', response);
        if (response.status !== 200) {
          await BackgroundGeolocation.stop();
        }
      });

      const motionChange: Subscription = BackgroundGeolocation.onMotionChange((event) => {});
      /// 2. ready the plugin.
      BackgroundGeolocation.ready({
        notification: {
          priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_DEFAULT,
          title: 'abc',
          text: 'abc uses your location to help ____',
        },
        isMoving: false,
        url: `api/ping`,
        autoSync: false,
        maxRecordsToPersist: 1,
        method: 'POST',
        elasticityMultiplier: 6,
        distanceFilter: 15,
        disableLocationAuthorizationAlert: !getAllowAllLocationUdf,

        headers: {
          Authorization: 'Bearer ' + token.authToken,
          timezone: moment.tz.guess(),
        },
        showsBackgroundLocationIndicator: true,
        httpRootProperty: '.',
        locationTemplate:
          '{"latitude":<%= latitude %>,"longitude":<%= longitude %>,"time":"<%= timestamp %>"}',
        backgroundPermissionRationale: {
          title: 'Allow abc to use the location',
          message:
            'abc utilizes your GPS.',
          positiveAction: 'Change to {backgroundPermissionOptionLabel}',
          negativeAction: 'Cancel',
        },
        desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
        stopTimeout: 1,
        debug: false,
        logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
        stopOnTerminate: false,
        startOnBoot: true,
      })
        .then((state) => {})
        .catch((err) => console.error(err));

      return () => {
        onProvider.remove();
        onLocation.remove();
        http.remove();
        motionChange.remove();
      };
    }
  }, []);

shubhlumber avatar May 06 '25 10:05 shubhlumber

I suggest you search your code for instance of calling .getCurrentPosition

christocracy avatar May 06 '25 12:05 christocracy

I'm not using .getCurrentPosition anywhere in my code. Instead, I'm using .onLocation in multiple places. Could this be the reason for the issue?

shubhlumber avatar May 06 '25 12:05 shubhlumber

I'm using .onLocation in multiple places. Could this be the reason for the issue?

No. .onLocation is merely an event-listener.

I suggest you capture plug-in logs where this phenomenon occurs. See api docs .emailLog

christocracy avatar May 06 '25 12:05 christocracy

We had added logs to be stored in our AWS S3 bucket using:

const uploadPreviousDayLogs = async () => {
    const endOfYesterday = moment().startOf('day').subtract(1, 'seconds').toDate();
    const startOfYesterday = moment(endOfYesterday).startOf('day').toDate();
    const apiUrl = `${config.API_URL}/api/geolog/timestamps/${startOfYesterday.getTime()}/log/`;

    try {
      await BackgroundGeolocation.logger.uploadLog(apiUrl);
      await BackgroundGeolocation.logger.destroyLog();
    } catch (error) {
      await BackgroundGeolocation.logger.destroyLog();
      console.log('[uploadLog] Error:', error);
      return;
    }
  };

  useEffect(() => {
    if (Platform.OS === 'android') {
      uploadPreviousDayLogs();
    }
  }, [status]);

However, this occasionally caused the app to crash, although it was not reproducible locally and occurred very rarely. Due to this instability, we have now removed the log capturing functionality.

shubhlumber avatar May 06 '25 12:05 shubhlumber

I just need to know one more thing: Does the Geolocation SDK store location data within the app? Because in our app, we haven’t written any code to explicitly remove location data from SQLite or any local storage.

shubhlumber avatar May 06 '25 12:05 shubhlumber

The plug-in inserts each recorded location into its SQLite db. When your Config.url returns a 200 response, the plug-in deletes that record from SQLite.

The db is typically empty.

A record can exist in the db only for Config.maxDaysToPersist before being expunged.

christocracy avatar May 06 '25 12:05 christocracy

This issue is stale because it has been open for 30 days with no activity.

github-actions[bot] avatar Jun 06 '25 02:06 github-actions[bot]

This issue was closed because it has been inactive for 14 days since being marked as stale.

github-actions[bot] avatar Jun 20 '25 02:06 github-actions[bot]