Skip to content

[Help Wanted]: Location tracking is not working when the Android app is terminated. #2424

@parthMerIcorp

Description

@parthMerIcorp

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions