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

Schedule is not working as expected in IOS

Open williamFportillo opened this issue 1 year ago • 1 comments

Your Environment

  • Plugin version: 4.16.2
  • Platform: iOS
  • OS version: 17.5.1
  • Device manufacturer / model: Iphone 13 pro max
  • React Native version (react-native -v): 0.72.6
  • Plugin config
{
     desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_NAVIGATION,
     distanceFilter: 30,
     stopTimeout: 5,
     debug: false,
     logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
     logMaxDays: 4,
     stopOnTerminate: false,
     startOnBoot: true,
     url: `${API_URL}/v2/location/${userIdParam || userId}`,
     showsBackgroundLocationIndicator: true,
     disableLocationAuthorizationAlert: true,
     maxDaysToPersist: 5,
     maxRecordsToPersist: -1,
     heartbeatInterval: 60,
     preventSuspend: true,
     foregroundService: true,
     disableElasticity: false,
     elasticityMultiplier: 3,
     stopOnStationary: false,
     disableMotionActivityUpdates: false,
     disableProviderChangeRecord: false,
     allowIdenticalLocations: false,
     locationUpdateInterval: 30000,
     fastestLocationUpdateInterval: 30000,
     enableHeadless: true,
     locationsOrderDirection: 'ASC',
     headers: {
       'x-app-secret': envVariables.APP_SECRET_HEADER,
     },
     extras: {
       sessionId: sessionId || uniqueId,
       os: Platform.OS === 'ios' ? 'ios' : 'android',
     },
     locationAuthorizationRequest: 'Always',
     scheduleUseAlarmManager: true,
     stationaryRadius: 250,
     isMoving: true,
}

Expected Behavior

When a schedule is configured and the plug-in is started that schedule will always be running.

Actual Behavior

When a schedule is configured and the plug-in is initialized it starts working as expected. But sometimes after a certain number of days or even hours the schedule stops working. It seems that it does not detect positions or something like that.

To set up a schedule I am currently doing this:

const resetSchedule = async (workdays?: Workdays): Promise<void> => {
    try {
      const schedule = workdays ? getSchedule(workdays) : []; // getSchedule(workdays) returns a string array like this: ["1 00:00-18:00"]
      await BackgroundGeolocation.stopSchedule();
      await BackgroundGeolocation.stop();
      await BackgroundGeolocation.setConfig({
        schedule,
      });
    } catch (error) {
      Alert.alert('Something went wrong while reset schedule');
    }
  };

  const configureSchedule = async ({
    workdays, isTrackingAlwaysActive = isTrackingAlways,
  }: ConfigureScheduleParamsType): Promise<void> => {
    try {
      await resetSchedule(workdays);
      if (toggleStatus && isTrackingAlwaysActive) {
        await BackgroundGeolocation.start();
      }

      if (toggleStatus && !isTrackingAlwaysActive) {
        await BackgroundGeolocation.startSchedule();
      }
    } catch (error) {
      // error
    }
  };

Steps to Reproduce

  1. Configure a schedule with BackgroundGeolocation.setConfig()
  2. Trigger . startSchedule
  3. Left the app in background.

Context

Wednesday, 26 June

The schedule was set up and worked as expected, tracking the day from 00:00 to 18:00. Screenshot 2024-06-27 at 10 26 23 AM copy

As you can see, the tracking service stopped sending the location at 17:59:34 to the server. The user continued moving but was out of schedule, this behavior is expected. On the map, you can see an image with the information of the last position sent to the server.

Screenshot 2024-06-27 at 10 30 06 AM copy

Thursday, 27 June

  1. This is the last log from Wednesday, 26 June.
  2. This is the first log for Thursday, 27 June.

Screenshot 2024-06-27 at 11 27 20 AM

The user continued moving, but none of the positions were sent to the server, and we are unable to see them in the logs as well. The last thing we saw on the logs was something related to geofences but we are not using the functionality (just in case).

Screenshot 2024-06-27 at 10 33 17 AM

One thing I noticed in the documentation is that the schedule property receives an array. However, in the logs, the schedule property is using () for the array. Thats something is just happening on iOS and it could just be how the log is being printed, but I thought I'd mention it in case it helps you as well.

Screenshot 2024-06-27 at 10 37 19 AM

Debug logs

Logs

I share with you the logs file because is too large.

Screenshot 2024-06-28 at 12 22 04 PM

background-geolocation.log-14.gz

williamFportillo avatar Jun 28 '24 18:06 williamFportillo

One thing I noticed in the documentation is that the schedule property receives an array. However, in the logs, the schedule property is using () for the array.

This is nothing to worry about. This is merely how an Obj-c NSArray#as_string method prints its contents to the logs.

christocracy avatar Jun 28 '24 18:06 christocracy

1. I opened the app on Thursday, 27 June, and then I stopped and started the tracking again (This was after getting the logs that I already shared at the beginning of the issue).

Screenshot 2024-07-01 at 9 52 45 AM

2. You can see the tracking started working

Screenshot 2024-07-01 at 9 54 21 AM

3. The service worked as expected the whole day, respecting the schedule that was set up.

Screenshot 2024-07-01 at 9 58 50 AM

4. The next day (Friday 28th ) the service got a location update (as expected)

Screenshot 2024-07-01 at 9 59 33 AM


5. ⚠️ Keep an eye on this: The config has the Schedule array set up.

Screenshot 2024-07-01 at 10 03 03 AM


6. Same as I described at the beginning of the issue, the tracking stopped working but the last thing printed on the logs was this Geofences table creation. (We aren't using the geofence feature, maybe is just a part of the process but I mentioned it because may be a hint)

Screenshot 2024-07-01 at 10 06 50 AM


7. The service didn't report any location the whole day of Friday 28th, then The service started again the next day on Saturday 29th.

Screenshot 2024-07-01 at 10 10 40 AM


8. ⚠️ But notice the service settings for the schedule, is empty, and the last logs were the geofences (again).

Screenshot 2024-07-01 at 10 12 38 AM

9. Sunday 30th, the same thing as Saturday, the service logged the settings, the schedule array was empty and the last thing printed on the logs was the Geofence table.

Screenshot 2024-07-01 at 10 15 56 AM

I share with you the full logs background-geolocation.log-15.gz

@christocracy

williamFportillo avatar Jul 01 '24 16:07 williamFportillo

Are you absolutely sure that your app is calling .ready(config) each time your app is launched, no matter what — even if your app is automatically launched by the OS in the background?

christocracy avatar Jul 01 '24 16:07 christocracy

1. index.js

import { AppRegistry } from 'react-native';
import BackgroundGeolocation from 'react-native-background-geolocation';
import App from './App';

AppRegistry.registerComponent(appName, () => App);

BackgroundGeolocation.registerHeadlessTask(HeadlessTask);

2. App.tsx

In my App.tsx exist a global component <Geolocator /> and this component execute once a function that i use to do BackgroundGeolocation.ready

import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import ErrorBoundary from './src/ErrorBoundary';
import Geolocator from '.src/Geolocator';

const App = () => (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
          <ErrorBoundary>
            <Geolocator />
            <MainStack />  {/* Handle my screens views */}
          </ErrorBoundary>
      </PersistGate>
    </Provider>
    );
  
export default App;

3. Geolocator.tsx

In this component i use a custom hook related with all the backgroungGeolocation functionality.

geolocatorConfig() execute BackgroundGeolocation.ready()

import { useEffect } from 'react';
import { useBackgroundGeolocation } from '@hooks';

const Geolocator = () => {
    const { geolocatorConfig } = useBackgroundGeolocation();

    useEffect(() => {
      /*
       ℹ️ The function that executes the BackgroundGeolocation.ready() 
       I think is called each time the app is launched, but I'm not sure 
       if it is because that is inside of this useEffect is prevented from being executed, 
       and I have to take it away from here. 
      */
      geolocatorConfig();
    }, []);

    return <></>;
};

export default Geolocator;

4. @hooks/useBackgroundGeolocation.ts

  1. geolocatorConfig execute BackgroundGeolocation.ready and this contain my current configuration.
  2. I have a button in the app that start/stop geolocation plug-in. Thus startGeolocation function start the plug-in.
  3. stopGeolocation function stop the plug-in,
  4. I have a section in the app where users can select if they want to use schedule or not. Thus configureSchedule execute a reset of the schedule and make a BackgroundGeolocation.setConfig to set the new schedule. This function also evaluate if is neccesary start the plug-in again.
import BackgroundGeolocation, { Location, State } from 'react-native-background-geolocation';
import { useAppDispatch, useAppSelector } from './useRedux.hook';

}
export const useBackgroundGeolocation = () => {
  const dispatch = useAppDispatch();
  const geolocatorSettings = useAppSelector((state) => state.geolocator.geolocatorSettings);
  const isTrackingAlways = useAppSelector((state) => state.geolocator.trackingAlways);
  const toggleStatus = useAppSelector((state) => state.geolocator.toggleStatus);

  const geolocatorConfig = (
    config: GeolocatorConfig = { userIdParam: undefined, sessionId: '' },
  ) => {
    const { userIdParam, sessionId } = config;
    // ℹ️ Here I'm calling the method .ready() from the plugin 
    BackgroundGeolocation.ready({
      desiredAccuracy: geolocatorSettings.desiredAccuracy,
      distanceFilter: geolocatorSettings.distanceFilter,
      stopTimeout: geolocatorSettings.stopTimeOut,
      debug: false,
      logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
      logMaxDays: 4,
      stopOnTerminate: geolocatorSettings.stopOnTerminate,
      startOnBoot: true,
      url: `${API_URL}/v2/location/${userIdParam || userId}`,
      showsBackgroundLocationIndicator: true,
      disableLocationAuthorizationAlert: true,
      maxDaysToPersist: 5,
      maxRecordsToPersist: -1,
      heartbeatInterval: 60 * geolocatorSettings.heartBeatInterval,
      preventSuspend: true,
      foregroundService: true,
      disableElasticity: geolocatorSettings.disableElasticity,
      elasticityMultiplier: geolocatorSettings.elasticityMultiplier,
      stopOnStationary: geolocatorSettings.stopOnStationary,
      disableMotionActivityUpdates:
        geolocatorSettings.disableMotionActivityUpdates,
      disableProviderChangeRecord: false,
      allowIdenticalLocations: geolocatorSettings.allowIdenticalLocations,
      locationUpdateInterval: geolocatorSettings.locationUpdateInterval * 1000,
      fastestLocationUpdateInterval:
        1000 * geolocatorSettings.fastestLocationUpdateInterval,
      enableHeadless: true,
      locationsOrderDirection: 'ASC',
      headers: {
        'x-app-secret': envVariables.APP_SECRET_HEADER,
      },
      extras: {
        sessionId: sessionId || uniqueId,
        os: Platform.OS === 'ios' ? 'ios' : 'android',
      },
      locationAuthorizationRequest: 'Always',
      scheduleUseAlarmManager: true,
      stationaryRadius: 250,
      isMoving: true,
      activityType: BackgroundGeolocation.ACTIVITY_TYPE_OTHER,
      backgroundPermissionRationale: {
        title: translate('backgroundPermissionRationaleTitle'),
        message: translate('backgroundPermissionRationaleMessage'),
        positiveAction: translate('backgroundPermissionRationalePositiveAction'),
        negativeAction: translate('cancel'),
      },
    }).catch();
  };

  const stopGeolocation = (navigateTo?: () => void) => {
    BackgroundGeolocation.getCurrentPosition({
      timeout: 30,
      maximumAge: 5000,
      desiredAccuracy: 10,
    }).then((position) => {
      const lastPosition = {
        ...position,
        event: 'gps_off',
      };
      sendLocations(lastPosition);
    });
    dispatch(changeToggleStatus(false));
    BackgroundGeolocation.stopSchedule();
    BackgroundGeolocation.stop();
    BackgroundGeolocation.destroyLocations();
    BackgroundGeolocation.removeListeners();
    navigateTo?.();
  };

  const initBackgroundGeolocation = async () => {
    const location = await BackgroundGeolocation.getCurrentPosition({
      timeout: 30,
      maximumAge: 5000,
      desiredAccuracy: 10,
    });
    const firstPosition = {
      ...location,
      event: 'gps_on',
    };
    sendLocations(firstPosition);
  };

  const startGeolocation = async (cb?: () => void) => {
    if (isTrackingAlways) {
      await BackgroundGeolocation.start();
    } else {
      await BackgroundGeolocation.startSchedule();
    }
    initBackgroundGeolocation();
    dispatch(changeToggleStatus(true));
    cb?.();
  };


  const resetSchedule = async (workdays?: Workdays): Promise<void> => {
    try {
      const schedule = workdays ? getSchedule(workdays) : [];
      await BackgroundGeolocation.stopSchedule();
      await BackgroundGeolocation.stop();
      await BackgroundGeolocation.setConfig({
        schedule,
      });
    } catch (error) {
      Alert.alert('Something went wrong while reset schedule');
    }
  };

  // ℹ️ This function is called when the user change the schedule on the app
  const configureSchedule = async ({
    workdays, isTrackingAlwaysActive = isTrackingAlways,
  }): Promise<void> => {
    try {
      await resetSchedule(workdays);
      if (toggleStatus && isTrackingAlwaysActive) {
        await BackgroundGeolocation.start();
      }

      if (toggleStatus && !isTrackingAlwaysActive) {
        await BackgroundGeolocation.startSchedule();
      }
    } catch (error) {
      // error
    }
  };

  return {
    stopGeolocation,
    geolocatorConfig,
    startGeolocation,
    getGeolocatorState,
    saveGeolocatorConfigOnDb,
    resetSchedule,
    configureSchedule,
    isSavingGeolocatorConfig: loading,
  };
};

Login.tsx

When i do login i call geolocatorConfig to set in BackgroundGeolocation.ready({}) a config with the values of the user.

import React, { useEffect, useRef, useState } from 'react';
import { useAppDispatch, useAppSelector, useBackgroundGeolocation, useTranslate } from '@hooks';
import { AxiosError } from 'axios';

export const Login = () => {
  const dispatch = useAppDispatch();
  const workdays = useAppSelector((state) => state.user.workdays);
  const fcmToken = useAppSelector((state) => state.user.fcmToken);
  const { geolocatorConfig } = useBackgroundGeolocation();
  const { execute } = authService.login();

  const validateSession = async (code: string): Promise<void> => {
    try {
      const result = await execute<LoginResponse>({
          phoneNumber,
          areaCode,
          otp: code,
          fcmToken,
          phoneOS: DeviceInfo.getSystemName(),
          phoneModel: DeviceInfo.getModel(),
      });
      if (result.status === 200) {
        /* ℹ️ Here I'm calling the method .ready() from the plugin again, 
        Not sure if this may cause an issue, but I don't think it does
        because the screen only appears once, and then should be called globally 
        in the `Geolocator` component
        */
        geolocatorConfig({
          userIdParam: result.data?.id,
          sessionId: result.data?.uniqueId!,
        });
      }
    } catch (err) {
        showAlert();
    }
  };

  return (<></>);
};

@christocracy This is how I am setting up the plug-in

williamFportillo avatar Jul 01 '24 17:07 williamFportillo

It’s a yes or no question. It’s not for me to analyze your code.

Are you absolutely sure that your app is calling .ready(config) each time your app is launched, no matter what — even if your app is automatically launched by the OS in the background?

christocracy avatar Jul 01 '24 17:07 christocracy

Yes, i'm calling it here

const Geolocator = () => {
    const { geolocatorConfig } = useBackgroundGeolocation();

    useEffect(() => {
      /*
       ℹ️ The function that executes the BackgroundGeolocation.ready() 
      */
      geolocatorConfig(); // BackgroundGeolocation.ready({ config }) 👈🏻
    }, []);

    return <></>;
};
const App = () => (
        <Geolocator />
    );
  
export default App;

williamFportillo avatar Jul 01 '24 18:07 williamFportillo

Hi guys, does anyone have an update on this? I am getting kind of the same issue, but on my end, it is happening with the location service always (tracking 24/7) if the devices are kept in the same place for 2 days without going out, on the third day the positions aren't sent to the server. btw same environment iOS

@christocracy @williamFportillo

iamdperez avatar Jul 08 '24 16:07 iamdperez

I am getting kind of the same issue

You’ve not provided any information. See the original post to learn the sort of information to provide.

christocracy avatar Jul 08 '24 16:07 christocracy

Hey, on my end, I have been experiencing the same problem on iOS. It works perfectly on Android. What do you think is happening on iOS? @christocracy

williamFportillo avatar Jul 08 '24 17:07 williamFportillo

It’s still not clear. You’re not providing any info. Eg plug-in Config.

christocracy avatar Jul 08 '24 17:07 christocracy

In the issue description, I shared the plug-in configuration, context, and complete logs. Is there anything else you need me to provide?

williamFportillo avatar Jul 08 '24 17:07 williamFportillo

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

github-actions[bot] avatar Aug 08 '24 01:08 github-actions[bot]

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

github-actions[bot] avatar Aug 22 '24 01:08 github-actions[bot]