transistorsoft / react-native-background-geolocation

Sophisticated, battery-conscious background-geolocation with motion-detection
http://shop.transistorsoft.com/pages/react-native-background-geolocation
MIT License
2.66k stars 426 forks source link

Plugin interfering with opining app from notification when killed and screen is locked #2203

Open AliHaidar110 opened 1 week ago

AliHaidar110 commented 1 week ago

Your Environment

PASTE_YOUR_CODE_HERE
// reducer
const setupLocationService = createAsyncThunk(
    "location/setupLocationService",
    async (
        {
            deviceId,
            isNewDevice,
        }: {
            deviceId: string;
            isNewDevice: boolean;
        },
        { rejectWithValue }
    ) => {
        const [, backgroundFetchConfigError] = await withErrorCatch(
            BackgroundFetch.configure(
                {
                    minimumFetchInterval: 15,
                    enableHeadless: true,

                    // android only
                    startOnBoot: true,
                    stopOnTerminate: false,
                },

                async (taskId) => {
                    const location = await BackgroundGeolocation.getCurrentPosition({
                        extras: {
                            event: "background-fetch",
                        },
                        timeout: 60,
                        samples: 1,
                    });

                    BackgroundFetch.finish(taskId);
                },

                async (taskId) => {
                    BackgroundFetch.finish(taskId);
                }
            )
        );

        if (backgroundFetchConfigError) {
            return rejectWithValue(backgroundFetchConfigError);
        }

        const [locationServiceState, locationServiceStateError] =
            await withErrorCatch(
                BackgroundGeolocation.ready({
                    // Debug
                    reset: true,
                    debug: false,
                    logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,

                    // Geolocation
                    desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
                    distanceFilter: 200,
                    // distanceFilter: 250, //? TODO: confirm based on client needs.

                    locationAuthorizationRequest: "Always",
                    backgroundPermissionRationale: {
                        title:
                            "Allow {applicationName} to access this device's location even when closed or not in use.",
                        message:
                            "This app collects location data to enable properties suggestion.",
                        positiveAction: 'Change to "{backgroundPermissionOptionLabel}"',
                        negativeAction: "Cancel",
                    },
                    notification: {
                        priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_HIGH,
                        title: "Skyline GO! Location Service",
                        text: "We're looking for best properties for you.",
                        // @todo: confirm if this is needed (recommend removing)
                        sticky: true,
                    },
                    // HTTP & Persistence
                    batchSync: false,
                    autoSync: true,
                    maxDaysToPersist: 1,
                    maxRecordsToPersist: 1,
                    // Application
                    stopOnTerminate: false,
                    startOnBoot: true,
                    // enableHeadless: true,

                    heartbeatInterval: 600,

                    // new
                    isMoving: true,
                })
            );

        if (locationServiceStateError) {
            return rejectWithValue(locationServiceStateError);
        }

        const [, locationServiceConfigError] = await withErrorCatch(
            withErrorCatch(
                BackgroundGeolocation.setConfig({
                    url: `${APP_CONFIG.API_URL}/devices/${deviceId}/locations`,
                    params: {
                        device_id: deviceId,
                    },
                })
            )
        );

        if (locationServiceConfigError) {
            return rejectWithValue(locationServiceConfigError);
        }

        if (isNewDevice) {
            const [locationServiceState, locationServiceStateError] =
                await withErrorCatch(BackgroundGeolocation.start());

            if (locationServiceStateError) {
                return rejectWithValue(locationServiceStateError);
            }

            return {
                locationServiceState,
            };
        }

        return {
            locationServiceState,
        };
    }
);
//MainAppNavigation.tsx
const setupDeviceAsync = async () => {
        // setup notification channel
        Notifications.setNotificationChannelAsync("default", {
            name: "default",
            importance: Notifications.AndroidImportance.MAX,
            vibrationPattern: [0, 250, 250, 250],
            lightColor: "#FF231F7C",
        });

        // setup device
        const [device, deviceSetupError] = await withErrorCatch(
            dispatch(deviceThunks.setupDevice({ deviceId: currentDeviceId })).unwrap()
        );

        if (deviceSetupError || !device) {
            return;
        }

        // setup location service
        const [, locationServiceError] = await withErrorCatch(
            dispatch(
                locationThunks.setupLocationService({
                    deviceId: device.deviceId,
                    isNewDevice: device.isNewDevice,
                })
            ).unwrap()
        );

        if (locationServiceError) {
            return;
        }

        // setup notification
        const [notification, notificationError] = await withErrorCatch(
            dispatch(
                notificationThunks.setupNotification({
                    deviceId: device.deviceId,
                })
            ).unwrap()
        );

        if (notificationError || !notification) {
            return;
        }

        const { expoToken, permissionStatus } = notification;

        // update device expo token and permission status
        const [, updateDeviceError] = await withErrorCatch(
            dispatch(
                deviceThunks.updateDeviceData({
                    deviceId: device.deviceId,
                    expo_token: expoToken,
                    permissions: permissionStatus,
                })
            ).unwrap()
        );

        if (updateDeviceError) {
            return;
        }

        // setup location subscriber
        const locationSubscriber = BackgroundGeolocation.onLocation(
            (location) => {
                const isMoving = store.getState().location.isMoving;

                dispatch(locationActions.updateLocation(location));

                isMoving &&
                    dispatch(
                        locationThunks.updateLocation({
                            deviceId: device.deviceId,
                            location,
                        })
                    ).unwrap();
            },
            (error) => {
                console.error("[onLocation] ERROR: ", error);
            }
        );

        const motionChangeSubscriber = BackgroundGeolocation.onMotionChange(
            (location) => {
                dispatch(locationActions.updateIsMoving(location.isMoving));
            }
        );

        const cleanup = () => {
            locationSubscriber.remove();
            motionChangeSubscriber.remove();
        };

        return cleanup;
    };

    useEffect(() => {
        if (isFirstTime.current) {
            isFirstTime.current = false;
            setupDeviceAsync().finally(() => {
                setIsLoading(false);
            });
        }
    }, []);
// App.tsx
export default function App() {
    return (
        <JotaiProvider store={myStore}>
            <Provider store={store}>
                <PersistGate loading={null} persistor={persistor}>
                    <MainAppNavigation />
                </PersistGate>
            </Provider>
        </JotaiProvider>
    );
}

Expected Behavior

When App is killed by swiping it and screen is locked by if you press on received notification from the lock screen without unlocking the app should open.

Actual Behavior

If app is killed and you do the above steps the app will not open but you can see it when you swipe up on home screen. But if you re-killed the app and then redid the above the app will open. Basically it will work correctly every other try

Steps to Reproduce

1. 2. 3. 4.

Context

I was trying to monitor user location and track them so they can receive notifications based on their location.

Debug logs

Logs ``` PASTE_YOUR_LOGS_HERE ``` one observation I had is that the app "main" shown in picture below will not lunch ``` // expected [01:56:31] I | ReactNativeJS ▶︎ Running "main [01:56:31] I | ReactNativeJS ▶︎ '🚀 ~ useEffect ~ lastNotificationResponse:', undefined ``` ``` // actual [01:56:31] I | ReactNativeJS ▶︎ Running "main [01:56:31] I | ReactNativeJS ▶︎ '🚀 ~ useEffect ~ lastNotificationResponse:', undefined ```