Rapsssito / react-native-background-actions

React Native background service library for running background tasks forever in Android & iOS.
MIT License
817 stars 116 forks source link

React Native Background Actions - App is Not Running in Background All the Time #232

Closed Rajkumar1454 closed 4 months ago

Rajkumar1454 commented 4 months ago

Hi,

We are developing a Delivery Partner App using React Native version 0.73.6. The core functionality of our app requires constant access and tracking of the user's current location, even when the app is closed or running in the background.

To achieve this, we have integrated the react-native-background-actions library, version 4.0.1, and followed all the steps outlined in the documentation for Android.

While the app does run in the background initially, we are facing issues with the app not staying in the background for an extended period across various Android devices. The duration for which the app remains active in the background is inconsistent and varies from device to device.

Here is a summary of our issue:

Code Example:

import { useEffect, useRef, useState } from 'react';
import { PermissionsAndroid, Platform, StyleSheet, Text, useAnimatedValue } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import Navigator from './main/navigations';
import BootSplash from "react-native-bootsplash";
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import BackgroundService from 'react-native-background-actions'
import { requestLocationPermission } from './main/components/fire_store';
import AsyncStorage from '@react-native-async-storage/async-storage';
const sleep = (time: any) => new Promise((resolve: any) => setTimeout(() => resolve(), time));
// You can do anything in your task such as network requests, timers and so on,
// as long as it doesn't touch UI. Once your task completes (i.e. the promise is resolved),
// React Native will go into "paused" mode (unless there are other tasks running,
// or there is a foreground app).
const veryIntensiveTask = async (taskDataArguments: any) => {
  // Example of an infinite loop task
  const { delay, current } = taskDataArguments;
  await new Promise(async (resolve) => {
    for (let i = 0; BackgroundService.isRunning(); i++) {
      console.log('count value============', i);
      // Fetch the updated value of current from AsyncStorage
      const storedCurrent = await AsyncStorage.getItem('currentLocation');
      const updatedCurrent = storedCurrent ? parseFloat(storedCurrent) : current;
      await requestLocationPermission();
      await BackgroundService.updateNotification({ taskDesc: `My count is running ${i}, lat:${updatedCurrent}` });
      await sleep(delay);
    }
  });
};
const options = {
  taskName: 'Example',
  taskTitle: 'ExampleTask title',
  taskDesc: 'ExampleTask description',
  taskIcon: {
    name: 'ic_launcher',
    type: 'mipmap',
  },
  color: '#ff00ff',
  linkingURI: 'yourSchemeHere://chat/jane', // See Deep Linking for more info
  parameters: {
    delay: 1000,  // 1 second 
    current: null, // This will be updated later
  },
};

const App = ((): JSX.Element => {
  const [current, setCurrent] = useState(null);
  useEffect(() => {
    const loadCurrent = async () => {
      try {
        const storedCurrent = await AsyncStorage.getItem('currentLocation');
        if (storedCurrent !== null) {
          setCurrent(parseFloat(storedCurrent));
        } else {
          setCurrent(null);
        }
      } catch (error) {
        console.error('Failed to load current from storage:', error);
      }
    };
    const interval = setInterval(loadCurrent, 1000);
    return () => clearInterval(interval);
  }, []);

  useEffect(() => {
    const init = async () => {
      startBackgroundService();
    };
    init();
  }, [current]); // Add current as a dependency

  const startBackgroundService = async () => {
    // const allPermissionsGranted = await requestAllPermissions();
    // if (!allPermissionsGranted) {
    //   Alert.alert("Permissions Required", "All permissions are required to start the background service.");
    //   return;
    // }
    const isRunning = await BackgroundService.isRunning();
    if (!isRunning) {
      await requestAllPermissions();
      try {
        options.parameters.current = current; // Update current in options
        console.log('Trying to start background service');
        await BackgroundService.start(veryIntensiveTask, options);
        console.log('Successful start!');
      } catch (e) {
        console.log('Error', e);
      }
    } else {
      console.log('Background service is already running');
    }
  };

  const requestAllPermissions = async () => {
    try {
      const fineLocationGranted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
      const backgroundLocationGranted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION);
      const notificationGranted = await requestNotificationPermission();
      return (
        fineLocationGranted === PermissionsAndroid.RESULTS.GRANTED &&
        backgroundLocationGranted === PermissionsAndroid.RESULTS.GRANTED &&
        notificationGranted
      );
    } catch (err) {
      console.log(err);
      return false;
    }
  };
  const requestNotificationPermission = async () => {
    if (Platform.OS === 'android') {
      // check if the platfrom is Android 13 or higher
      if (Platform.Version >= 33) {
        const permission = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
        return (permission === PermissionsAndroid.RESULTS.GRANTED)

      }
      return true; // Permissions are granted by default for Android versions below 13
    } else {
      return true;
    }
  };
  return (
    <BottomSheetModalProvider>
      <GestureHandlerRootView style={{ flex: 1 }}>
        <SafeAreaProvider>
          <Navigator />
        </SafeAreaProvider>
      </GestureHandlerRootView>
    </BottomSheetModalProvider>
  )
})
export default App;

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="package.name">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
            android:launchMode="singleTask"
            android:windowSoftInputMode="adjustResize"
            android:exported="true"
            android:directBootAware="true"
            android:theme="@style/BootTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter android:label="filter_react_native">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="exampleScheme" />
            </intent-filter>
        </activity>
        <service 
            android:directBootAware="true" 
            android:exported="true"
            android:foregroundServiceType="location|mediaPlayback"
            android:enabled="true"
            android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask">
            <intent-filter>
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
            </intent-filter>
        </service>
    </application>
</manifest>

Steps to Reproduce:

  1. Integrate react-native-background-actions in a React Native app.
  2. Follow the setup instructions for Android as per the documentation.
  3. Start the background service to track location.
  4. Observe the app's behavior on different Android devices.

Expected Behavior: The app should run in the background indefinitely, consistently tracking the user's location across all Android devices.

Actual Behavior: The app runs in the background initially but stops after an unpredictable duration, varying across different Android devices.

Additional Information:

We would greatly appreciate any guidance or solutions to ensure our app remains active in the background consistently across all devices. What other permissions or configurations do we need to keep the app running in the background all the time?

Thank you!


Link to the react-native-background-actions documentation: react-native-background-actions


Please let me know if any further information is required. @Rapsssito @shergin @mdvacca

Rapsssito commented 4 months ago

@Rajkumar1454, please, do not spam other repositories and other users. This is common, as the OS has absolute control over the background processes of the device. See https://github.com/Rapsssito/react-native-background-actions/issues/22#issuecomment-628737527