-
Notifications
You must be signed in to change notification settings - Fork 440
Open
Description
Required Reading
- Confirmed
Plugin Version
4.19.0
Mobile operating-system(s)
- iOS
- Android
Device Manufacturer(s) and Model(s)
Armor 20WT
Device operating-systems(s)
Android 12
React Native / Expo version
0.80.2
What do you require assistance about?
Issue:
Location tracking is not working when the Android app is terminated.
Details:
I have implemented background location tracking and registered the Headless Task using:
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
It works when the app is in the foreground or background location tracking, but the location tracking and API calls stop when the app is terminated.
Request:
Please help me fix location tracking when the app is terminated on Android.
[Optional] Plugin Code and/or Config
useEffect(() => {
activeShiftRef.current = activeShift;
if (isInitialized.current) {
return;
}
if (
isClockedIn !== true &&
activeShiftRef?.current?.siteId?.userTrack !== 'active'
) {
return;
}
isInitialized.current = true;
const initLocation = async () => {
await BackgroundGeolocation.requestPermission().then(status => {
console.log('Permission status:', status);
});
await BackgroundGeolocation.ready(
{
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_LOW,
stopTimeout: 1,
distanceFilter: 30,
stopOnTerminate: false,
disableMotionActivityUpdates: false,
startOnBoot: true,
locationAuthorizationRequest: 'Always',
preventSuspend: true,
showsBackgroundLocationIndicator: true,
foregroundService: true,
notification: {
title: 'Shift Tracking',
text: 'Location tracking active',
color: '#4CAF50',
priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_HIGH,
},
},
state => {
console.log('[ready] state:', state);
if (
!state.enabled &&
isClockedIn === true &&
activeShiftRef?.current?.siteId?.userTrack === 'active'
) {
BackgroundGeolocation.start();
isTrackingStarted.current = true;
console.log('Background tracking started.');
}
},
);
await BackgroundGeolocation.onLocation(async location => {
const state = await BackgroundGeolocation.getProviderState();
if (!state.enabled) {
return;
}
if (!isClockedIn) {
return;
}
if (
location?.coords?.latitude !== undefined &&
location?.coords?.longitude !== undefined
) {
const now = Date.now();
const trackingIntervalMin = activeShiftRef?.current?.siteId?.trackingLogTimerMinute || radarTrackingTime / 60;
const trackingIntervalMs = trackingIntervalMin * 60 * 1000;
if (now - lastLocationTime.current < trackingIntervalMs) {
return;
}
lastLocationTime.current = now;
const stopped = location?.is_moving;
const shouldSend =
isStoppedRef.current === null ||
(isStoppedRef.current === true && stopped === false) ||
(isStoppedRef.current === false && stopped === false) ||
(isStoppedRef.current === false && stopped === true);
if (shouldSend) {
await setLastUserTrack({ ...location });
await geofanceTrack(location);
await delay(5000);
}
}
});
};
if (
isClockedIn &&
activeShiftRef?.current?.siteId?.userTrack === 'active'
) {
initLocation();
}
return () => {
if (isTrackingStarted.current) {
isTrackingStarted.current = false;
isInitialized.current = false;
BackgroundGeolocation.removeAllListeners();
}
};
}, [isClockedIn]);
useEffect(() => {
const addGeofence = async () => {
if (
!geoFancingData?.length ||
!isClockedIn ||
activeShiftRef?.current?.siteId?.userTrack !== 'active'
)
return;
const state = await BackgroundGeolocation.getProviderState();
if (!state.enabled) {
return;
}
if (!isClockedIn) return;
const geofences = geoFancingData?.map(item => ({
identifier: item.loactionName,
notifyOnEntry: true,
notifyOnExit: true,
vertices: item.fencingBound.map(p => [
parseFloat(p.lat),
parseFloat(p.lng),
]),
}));
BackgroundGeolocation.getGeofences().then(existing => {
const existingIds = existing.map(g => g.identifier);
const toAdd = geofences.filter(
g => !existingIds.includes(g.identifier),
);
if (toAdd?.length > 0) {
BackgroundGeolocation.addGeofences(toAdd)
.then(() => console.log(`:white_tick: Added ${toAdd.length} new geofences`))
.catch(err => console.log(':x: Geofence error:', err));
} else {
console.log(':zap: Geofences already registered, skipping add');
}
});
};
addGeofence();
const appStateListener = AppState.addEventListener(
'change',
nextAppState => {
if (
nextAppState === 'active' &&
isClockedIn &&
activeShiftRef?.current?.siteId?.userTrack === 'active'
) {
addGeofence();
}
},
);
return () => {
appStateListener.remove();
};
}, [geoFancingData, isClockedIn, activeShift]);
useEffect(() => {
if (!isClockedIn && activeShiftRef?.current?.siteId?.userTrack !== 'active')
return;
const geofenceSubscription = BackgroundGeolocation.onGeofence(
async geofence => {
const now = Date.now();
if (now - lastLogTime.current < 60000) {
return;
}
lastLogTime.current = now;
const geofenceId = geoFancingDataRef?.current?.find(
item => item?.loactionName === geofence?.identifier,
)?._id;
if (
(geofence?.action == 'ENTER' && lastTrackLog === null) ||
lastTrackLog === 'GEOFENCE_EXIT'
) {
await setLastTrackLog('GEOFENCE_ENTER');
await geofanceTrack(geofence?.location, 'GEOFENCE_ENTER', geofenceId);
await delay(5000);
} else if (
geofence?.action == 'EXIT' &&
lastTrackLog === 'GEOFENCE_ENTER'
) {
await setLastTrackLog('GEOFENCE_EXIT');
await geofanceTrack(geofence?.location, 'GEOFENCE_EXIT', geofenceId);
await delay(5000);
}
},
);
return () => {
geofenceSubscription?.remove();
};
}, [isClockedIn, lastTrackLog, activeShiftRef]);
// create new file root path index.headless.js
import BackgroundGeolocation from 'react-native-background-geolocation';
import { addUserTrackRecord } from './src/redux/actions';
const geofanceTrack = async (
data,
type,
geofenceId,
) => {
const now = new Date();
const formattedNow = moment(now).format('YYYY-MM-DDTHH:mm:ss.000[Z]');
const shift = await getAsyncStorageItem('shiftData')
const payload = {
shiftId: shift?._id,
stopped: !data?.is_moving,
trackFrom: 'GeoFencing',
source: type ? type : 'BACKGROUND_LOCATION',
...(type &&
geofenceId && {
geoFenceId: geofenceId,
}),
location: {
accuracy: `${data?.coords?.accuracy}`,
altitude: `${data?.coords?.altitude}`,
latitude: `${data?.coords?.latitude}`,
longitude: `${data?.coords?.longitude}`,
speed: `${data?.coords?.speed}`,
},
timeDate: formattedNow,
};
console.log('payload', payload);
try {
if (shift?._id !== undefined && Object.keys(shift)?.length > 0) {
const response = await addUserTrackRecord(payload);
if (response?.data?.success) {
console.log('response?.data HeadlessTask', response?.data);
return response;
}
}
} catch (err) {
console.log('Failed to send location HeadlessTask', err);
return err;
}
};
const HeadlessTask = async event => {
console.log('[BackgroundGeolocation HeadlessTask]', event.name, event.params);
switch (event.name) {
case 'location':
const { location } = event.params;
const now = new Date();
const formattedNow = moment(now).format('YYYY-MM-DDTHH:mm:ss.000[Z]');
const payload = {
shiftId: 'YOUR_SHIFT_ID', // get from storage or context if needed
stopped: !location.is_moving,
trackFrom: 'GeoFencing',
source: 'BACKGROUND_LOCATION',
location: {
accuracy: `${location.coords.accuracy}`,
altitude: `${location.coords.altitude}`,
latitude: `${location.coords.latitude}`,
longitude: `${location.coords.longitude}`,
speed: `${location.coords.speed}`,
},
timeDate: formattedNow,
};
console.log('[HeadlessTask] Payload:', payload);
try {
if (
location?.coords?.latitude !== undefined &&
location?.coords?.longitude !== undefined
) {
await geofanceTrack(location);
await delay(5000);
}
} catch (error) {
console.log('HeadlessTask API failed:', error);
}
break;
case 'motionchange':
console.log('[headless] motionchange:', event.params);
break;
case 'geofence':
console.log('[headless] geofence:', event.params);
break;
}
};
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
// index.js
import App from './App';
import { name as appName } from './app.json';
import Geolocation from "react-native-geolocation-service";
import './index.headless'; // call headless task
AppRegistry.registerComponent(appName, () => App);[Optional] Relevant log output
Metadata
Metadata
Assignees
Labels
No labels