Rapsssito / react-native-background-actions

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

Fatal Exception: java.lang.RuntimeException want to use foregroundServiceType="connectedDevice" #240

Open Faizan-Hurekatek opened 2 months ago

Faizan-Hurekatek commented 2 months ago

App crashes we want to use foregroundServiceType="connectedDevice"

Unable to start service com.asterinet.react.bgactions.RNBackgroundActionsTask@243c415 with Intent { cmp=com.iwelhealth.cgmpal/com.asterinet.react.bgactions.RNBackgroundActionsTask (has extras) mCallingUid=10388 }: android.app.ForegroundServiceStartNotAllowedException: Service.startForeground() not allowed due to mAllowStartForeground false: service com.iwelhealth.cgmpal/com.asterinet.react.bgactions.RNBackgroundActionsTask android.app.ActivityThread.handleServiceArgs

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"

<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:extractNativeLibs="true" android:screenOrientation="portrait" android:theme="@style/AppTheme"> <service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" android:permission="android.permission.FOREGROUND_SERVICE" android:foregroundServiceType="connectedDevice" android:exported="false" />

<meta-data
  android:name="com.auth0.client.android.scheme"
  android:value="https://iwell.page.link?utm_campaign=banner&amp;apn=com.iwell&amp;ibi=com.clovahealth.clova&amp;isi=6448808039&amp;link=https%3A%2F%2Finvertase.io%2Fterralogin" />
<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:screenOrientation="portrait"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="app.clovahealth.com"
      android:pathPrefix="/cgm/callback" />
    <data android:scheme="https" android:host="iwell.page.link" />
    <data android:scheme="https" android:host="iwell.page.link" />
  </intent-filter>
</activity>

package com.iwelhealth.cgmpal

import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate import android.os.Bundle // here

class MainActivity : ReactActivity() {

/**

  • Returns the name of the main component registered from JavaScript. This is used to schedule
  • rendering of the component. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(null) } override fun getMainComponentName(): String = "iWelCGMPal"

    /**

  • Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
  • which allows you to enable New Architecture with a single boolean flags [fabricEnabled] */ override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) } import React, {useEffect, useState} from 'react'; import { StyleSheet, SafeAreaView, KeyboardAvoidingView, Platform, Alert, Linking, PermissionsAndroid, } from 'react-native'; import {Navigation} from './navigation/Navigation'; import {StatusBar} from 'expo-status-bar'; import {NavigationContainer} from '@react-navigation/native'; import BackgroundService from 'react-native-background-actions'; import CGMDataService from './services/cgmService'; import AsyncStorage from '@react-native-async-storage/async-storage'; import {PERMISSIONS, requestMultiple} from 'react-native-permissions'; import DeviceInfo from 'react-native-device-info';

// Define your background task function const veryIntensiveTask = async () => { const {getCGMData} = CGMDataService();

// While the background service is running while (BackgroundService.isRunning()) { console.log('Running background task!');

// Call the CGM data fetching function
await getCGMData();

// Simulate delay for continuous task running

} };

// Options for the background service const options = { taskName: 'iWelCGMPal', taskTitle: 'iWelCGMPal', taskDesc: 'CGM Glucose Data description', taskIcon: { name: 'ic_launcher_notification', type: 'mipmap', }, linkingURI: 'yourSchemeHere://chat/jane', parameters: { delay: 60000, }, };

const AppEntry = () => { // State for token const [token, setToken] = useState(null); const [userid, setUserid] = useState(null);

// Function to check AsyncStorage for token regularly const checkHexArray = async () => { const savedToken = await AsyncStorage.getItem('token'); const savedUserid = await AsyncStorage.getItem('userid'); if (savedToken !== token && savedUserid !== userid) { setToken(savedToken); setUserid(savedUserid); // Update state only if it has changed console.log('Device ID (token) updated:', savedToken, savedUserid); } };

useEffect(() => { // Fetch token on mount checkHexArray();

// Start a polling interval to check AsyncStorage for updates
const intervalId = setInterval(() => {
  checkHexArray();
}, 5000); // Poll every 5 seconds (adjust as necessary)

// Cleanup interval on component unmount
return () => clearInterval(intervalId);

}, []);

const requestPermissions = async () => { try { if (Platform.OS === 'android') { const apiLevel = await DeviceInfo.getApiLevel();

    if (apiLevel < 31) {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        {
          title: 'Location Permission',
          message: 'Bluetooth Low Energy requires Location',
          buttonNeutral: 'Ask Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK',
        },
      );

      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        return true;
      } else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
        Alert.alert(
          'Permission Denied',
          'Please go to settings and enable Location And Nearby Devices permissions.',
          [
            {
              text: 'Go to Settings',
              onPress: () => Linking.openSettings(),
            },
            {text: 'Cancel', style: 'cancel'},
          ],
        );
      }
      return false;
    } else {
      const result = await requestMultiple([
        PERMISSIONS.ANDROID.BLUETOOTH_SCAN,
        PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
        PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
      ]);

      const isGranted =
        result['android.permission.BLUETOOTH_CONNECT'] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        result['android.permission.BLUETOOTH_SCAN'] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        result['android.permission.ACCESS_FINE_LOCATION'] ===
          PermissionsAndroid.RESULTS.GRANTED;

      if (isGranted) {
        return true;
      } else {
        const neverAskAgain =
          result['android.permission.BLUETOOTH_CONNECT'] ===
            PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN ||
          result['android.permission.BLUETOOTH_SCAN'] ===
            PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN ||
          result['android.permission.ACCESS_FINE_LOCATION'] ===
            PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN;

        if (neverAskAgain) {
          Alert.alert(
            'Permission Denied',
            'Please go to settings and enable Bluetooth and location permissions.',
            [
              {
                text: 'Go to Settings',
                onPress: () => Linking.openSettings(),
              },
              {text: 'Cancel', style: 'cancel'},
            ],
          );
        }
        return false;
      }
    }
  } else {
    return true;
  }
} catch (err) {
  console.error(err);
  return false;
}

};

useEffect(() => { if (token) { // Start background service when token is available const startBackgroundService = async () => { try { const isGrant = await requestPermissions(); if (isGrant) { if (!BackgroundService.isRunning()) { await BackgroundService.start(veryIntensiveTask, options); console.log('Background service started'); } } else { console.log('no permission granted '); } } catch (e) { console.error('Error starting background service:', e); } };

  startBackgroundService();
} else {
  // Stop background service when token is not available
  if (BackgroundService.isRunning()) {
    BackgroundService.stop();
    console.log('Background service stopped due to missing token');
  }
}

}, [token, userid]); // Re-run when token changes

return (

); };

export default AppEntry;

const styles = StyleSheet.create({ root: { flex: 1, }, }); "react": "18.2.0", "react-native": "0.73.6", "react-native-background-actions": "^4.0.1",

rakshitbharat commented 1 month ago

I can confirm this is a bug, it ended my prod build

FaizyQadri commented 1 month ago

It's fixed it was just we have to allow permission at the beginning only