Schedule is not working as expected in IOS
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
- Configure a schedule with BackgroundGeolocation.setConfig()
- Trigger . startSchedule
- 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.
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.
Thursday, 27 June
- This is the last log from Wednesday, 26 June.
- This is the first log for Thursday, 27 June.
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).
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.
Debug logs
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.
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).
2. You can see the tracking started working
3. The service worked as expected the whole day, respecting the schedule that was set up.
4. The next day (Friday 28th ) the service got a location update (as expected)
5. ⚠️ Keep an eye on this: The config has the Schedule array set up.
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)
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.
8. ⚠️ But notice the service settings for the schedule, is empty, and the last logs were the geofences (again).
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.
I share with you the full logs background-geolocation.log-15.gz
@christocracy
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?
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
-
geolocatorConfigexecuteBackgroundGeolocation.readyand this contain my current configuration. - I have a button in the app that start/stop geolocation plug-in. Thus
startGeolocationfunction start the plug-in. -
stopGeolocationfunction stop the plug-in, - I have a section in the app where users can select if they want to use schedule or not. Thus
configureScheduleexecute a reset of the schedule and make aBackgroundGeolocation.setConfigto 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
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?
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;
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
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.
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
It’s still not clear. You’re not providing any info. Eg plug-in Config.
In the issue description, I shared the plug-in configuration, context, and complete logs. Is there anything else you need me to provide?
This issue is stale because it has been open for 30 days with no activity.
This issue was closed because it has been inactive for 14 days since being marked as stale.