Git Product home page Git Product logo

capacitor-background-runner's Introduction

@capacitor/background-runner

Background Runner provides an event-based standalone JavaScript environment for executing your Javascript code outside of the web view.

Install

npm install @capacitor/background-runner
npx cap sync

Background Runner has support for various device APIs that require permission from the user prior to use.

iOS

On iOS you must enable the Background Modes capability.

Enable Background Mode Capability in Xcode

Once added, you must enable the Background fetch and Background processing modes at a minimum to enable the ability to register and schedule your background tasks.

If you will be making use of Geolocation or Push Notifications, enable Location updates or Remote notifications respectively.

Configure Background Modes in Xcode

After enabling the Background Modes capability, add the following to your app's AppDelegate.swift:

At the top of the file, under import Capacitor add:

import CapacitorBackgroundRunner
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // ....
    BackgroundRunnerPlugin.registerBackgroundTask()
    BackgroundRunnerPlugin.handleApplicationDidFinishLaunching(launchOptions: launchOptions)
    // ....

    return true
}

To allow the Background Runner to handle remote notifications, add the following:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        // ....
        BackgroundRunnerPlugin.dispatchEvent(event: "remoteNotification", eventArgs: userInfo) { result in
            switch result {
            case .success:
                completionHandler(.newData)
            case .failure:
                completionHandler(.failed)
            }
        }
    }

Geolocation

Apple requires privacy descriptions to be specified in Info.plist for location information:

  • NSLocationAlwaysUsageDescription (Privacy - Location Always Usage Description)
  • NSLocationWhenInUseUsageDescription (Privacy - Location When In Use Usage Description)

Read about Configuring Info.plist in the iOS Guide for more information on setting iOS permissions in Xcode

Android

Insert the following line to android/app/build.gradle:

...

repositories {
    flatDir{
        dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
+		dirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'
    }
}
...

If you are upgrading from 1.0.5 with an existing Android project, be sure to delete the android-js-engine-release.aar from android/src/main/libs.

Geolocation

This API requires the following permissions be added to your AndroidManifest.xml:

<!-- Geolocation API -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app requires GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware.

Local Notifications

Android 13 requires a permission check in order to send notifications. You are required to call checkPermissions() and requestPermissions() accordingly.

On Android 12 and older it won't show a prompt and will just return as granted.

Starting on Android 12, scheduled notifications won't be exact unless this permission is added to your AndroidManifest.xml:

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

Note that even if the permission is present, users can still disable exact notifications from the app settings.

Read about Setting Permissions in the Android Guide for more information on setting Android permissions.

About Background Runner

During the course of building complex applications, its sometimes necessary to perform work while the application is not in the foreground. The challenge with standard Capacitor applications is that the webview is not available when these background events occur, requiring you to write native code to handle these events. This is where the Background Runner plugin comes in.

Background Runner makes it easy to write JavaScript code to handle native background events. All you need to do is create your runner JavaScript file and define your configuration, then the Background Runner plugin will automatically configure and schedule a native background task that will be executed according to your config and the rules of the platform. No modification to your UI code is necessary.

Using Background Runner

Background Runner contains a headless JavaScript environment that calls event handlers in javascript file that you designate in your capacitor.config.ts file. If the runner finds a event handler corresponding to incoming event in your runner file, it will execute the event handler, then shutdown once resolve() or reject() are called (or if the OS force kills your process).

Example Runner JS File

addEventListener('myCustomEvent', (resolve, reject, args) => {
  console.log('do something to update the system here');
  resolve();
});

addEventListener('myCustomEventWithReturnData', (resolve, reject, args) => {
  try {
    console.log('accepted this data: ' + JSON.stringify(args.user));

    const updatedUser = args.user;
    updatedUser.firstName = updatedUser.firstName + ' HELLO';
    updatedUser.lastName = updatedUser.lastName + ' WORLD';

    resolve(updatedUser);
  } catch (err) {
    reject(err);
  }
});

addEventListener('remoteNotification', (resolve, reject, args) => {
  try {
    console.log('received silent push notification');

    CapacitorNotifications.schedule([
      {
        id: 100,
        title: 'Enterprise Background Runner',
        body: 'Received silent push notification',
      },
    ]);

    resolve();
  } catch (err) {
    reject();
  }
});

Calling resolve() \ reject() is required within every event handler called by the runner. Failure to do this could result in your runner being killed by the OS if your event is called while the app is in the background. If the app is in the foreground, async calls to dispatchEvent may not resolve.

For more real world examples of using Background Runner, check out the Background Runner Test App.

Configuring Background Runner

On load, Background Runner will automatically register a background task that will be scheduled and run once your app is backgrounded.

Prop Type Description Since
label string The name of the runner, used in logs. 1.0.0
src string The path to the runner JavaScript file, relative to the app bundle. 1.0.0
event string The name of the event that will be called when the OS executes the background task. 1.0.0
repeat boolean If background task should repeat based on the interval set in interval. 1.0.0
interval number The number of minutes after the the app is put into the background in which the background task should begin. If repeat is true, this also specifies the number of minutes between each execution. 1.0.0
autoStart boolean Automatically register and schedule background task on app load. 1.0.0

Examples

In capacitor.config.json:

{
  "plugins": {
    "BackgroundRunner": {
      "label": "com.example.background.task",
      "src": "runners/background.js",
      "event": "myCustomEvent",
      "repeat": true,
      "interval": 15,
      "autoStart": true
    }
  }
}

In capacitor.config.ts:

/// <reference types="@capacitor/background-runner" />

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  plugins: {
    BackgroundRunner: {
      label: "com.example.background.task",
      src: "runners/background.js",
      event: "myCustomEvent",
      repeat: true,
      interval: 15,
      autoStart: true,
    },
  },
};

export default config;

JavaScript API

Background Runner does not execute your Javascript code in a browser or web view, therefore the typical Web APIs you may be used to may not be available. This includes DOM APIs nor ability to interact with your application's DOM.

Below is a list of the available Web APIs provided in Background Runner:

In addition to the standard Web APIs, Background Runner also supports a number of custom Capacitor APIs custom APIs that expose relevant mobile device functionality

Runner Lifetimes

Currently, the runners are designed for performing periodic bursts of work while your app is in the background, or for executing asynchronous work in a thread separate from your UI while your app is in the foreground. As a result, runners are not long lived. State is not maintained between calls to events in the runner. Each call to dispatchEvent() creates a new context in which your runner code is loaded and executed, and once resolve() or reject() is called, the context is destroyed.

Android Battery Optimizations

Some Android vendors offer built-in battery optimization settings that go beyond what stock Android provides. Some of these optimizations must be disabled by your end users in order for your background tasks to work properly.

Visit Don't kill my app! for more information on the affected manufacturers and steps required by your users to adjust the settings.

Limitations of Background Tasks

It’s not possible to run persistent, always running background services on mobile operating systems. Due to the limitations imposed by iOS and Android designed to reduce battery and data consumption, background tasks are constrained with various limitations that you must keep in mind while designing and implementing your background task.

iOS

  • Each invocation of your task has approximately up to 30 seconds of runtime before you must call completed() or your task is killed.
  • While you can set an interval to define when your task runs after the app is backgrounded, or how often it should run, this is not guaranteed. iOS will determine when and how often you task will ultimately run, determined in part by how often you app is used.
  • Background tasks are not executed in the simulator.

Android

  • Your task has a maximum of 10 minutes to perform work, but to keep your task cross platform compatible, you should limit your work to 30 seconds at most.
  • Repeating background tasks have a minimal interval of at least 15 minutes. Similar to iOS, any interval you request may not be hit exactly - actual execution time is subject to OS battery optimizations and other heuristics.

API

checkPermissions()

checkPermissions() => any

Check permissions for the various Capacitor device APIs.

Returns: any

Since: 1.0.0


requestPermissions(...)

requestPermissions(options: RequestPermissionOptions) => any

Request permission to display local notifications.

Param Type
options RequestPermissionOptions

Returns: any

Since: 1.0.0


dispatchEvent(...)

dispatchEvent<T = void>(options: DispatchEventOptions) => any

Dispatches an event to the configured runner.

Param Type
options DispatchEventOptions

Returns: any

Since: 1.0.0


Interfaces

PermissionStatus

Prop Type
geolocation PermissionState
notifications PermissionState

RequestPermissionOptions

Prop Type
apis {}

DispatchEventOptions

Prop Type Description Since
label string The runner label to dispatch the event to 1.0.0
event string The name of the registered event listener. 1.0.0
details { [key: string]: any; }

Type Aliases

PermissionState

'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'

API

'geolocation' | 'notifications'

Capacitor API

Interfaces

CapacitorDevice

Get information on the device, such as network connectivity and battery status.

Prop Type Description Since
getBatteryStatus () => BatteryStatus Get the current battery status for the device. 1.0.0
getNetworkStatus () => NetworkStatus Get the current network status for the device. 1.0.0

BatteryStatus

Prop Type
batteryLevel number
isCharging boolean

NetworkStatus

Prop Type
connected boolean
connectionType string

CapacitorKV

A simple string key / value store backed by UserDefaults on iOS and Shared Preferences on Android.

Prop Type Description Since
set (key: string, value: string) => void Set a string value with the given key. 1.0.0
get (key: string) => { value: string; } Get a string value for the given key. 1.0.0
remove (key: string) => void Remove a value with the given key. 1.0.0

CapacitorNotifications

Send basic local notifications.

Prop Type Description Since
schedule (options: {}) => void Schedule a local notification 1.0.0

NotificationScheduleOptions

Prop Type Description Since
id number The notification identifier. On Android it's a 32-bit int. So the value should be between -2147483648 and 2147483647 inclusive. 1.0.0
title string The title of the notification. 1.0.0
body string The body of the notification, shown below the title. 1.0.0
scheduleAt Date Date to send this notification. 1.0.0
sound string Name of the audio file to play when this notification is displayed. Include the file extension with the filename. On iOS, the file should be in the app bundle. On Android, the file should be in res/raw folder. Recommended format is .wav because is supported by both iOS and Android. Only available for iOS and Android < 26. For Android 26+ use channelId of a channel configured with the desired sound. If the sound file is not found, (i.e. empty string or wrong name) the default system notification sound will be used. If not provided, it will produce the default sound on Android and no sound on iOS. 1.0.0
actionTypeId string Associate an action type with this notification. 1.0.0
threadIdentifier string Used to group multiple notifications. Sets threadIdentifier on the UNMutableNotificationContent. Only available for iOS. 1.0.0
summaryArgument string The string this notification adds to the category's summary format string. Sets summaryArgument on the UNMutableNotificationContent. Only available for iOS. 1.0.0
group string Used to group multiple notifications. Calls setGroup() on NotificationCompat.Builder with the provided value. Only available for Android. 1.0.0
groupSummary string If true, this notification becomes the summary for a group of notifications. Calls setGroupSummary() on NotificationCompat.Builder with the provided value. Only available for Android when using group. 1.0.0
extra any Set extra data to store within this notification. 1.0.0
ongoing boolean If true, the notification can't be swiped away. Calls setOngoing() on NotificationCompat.Builder with the provided value. Only available for Android. 1.0.0
autoCancel boolean If true, the notification is canceled when the user clicks on it. Calls setAutoCancel() on NotificationCompat.Builder with the provided value. Only available for Android. 1.0.0
largeBody string Sets a multiline text block for display in a big text notification style. 1.0.0
summaryText string Used to set the summary text detail in inbox and big text notification styles. Only available for Android. 1.0.0
smallIcon string Set a custom status bar icon. If set, this overrides the smallIcon option from Capacitor configuration. Icons should be placed in your app's res/drawable folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. 1.0.0
largeIcon string Set a large icon for notifications. Icons should be placed in your app's res/drawable folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. 1.0.0
channelId string Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls setChannelId() on NotificationCompat.Builder with the provided value. Only available for Android 26+. 1.0.0

CapacitorGeolocation

Get access to device location information.

Prop Type Description Since
getCurrentPosition () => GetCurrentPositionResult Get the device's last known location 1.0.0

GetCurrentPositionResult

Prop Type Description Since
latitude number Latitude in decimal degrees 1.0.0
longitude number longitude in decimal degrees 1.0.0
accuracy number Accuracy level of the latitude and longitude coordinates in meters 1.0.0
altitude number | null The altitude the user is at (if available) 1.0.0
altitudeAccuracy number | null Accuracy level of the altitude coordinate in meters, if available. Available on all iOS versions and on Android 8.0+. 1.0.0
speed number | null The speed the user is traveling (if available) 1.0.0
heading number | null The heading the user is facing (if available) 1.0.0

CapcacitorWatch

Interact with a watch paired with this app

sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application. They could be used if a native watch app is developed as a companion app to a Capacitor app

Prop Type Description
sendMessage (options: []) => void Sends a message to the watch with the sendMessage() WCSession delegate method This has no effect on a CapacitorWatch watch app
transferUserInfo (options: []) => void Sends information to the watch with the transferUserInfo() WCSession delegate method This has no effect on a CapacitorWatch watch app
updateApplicationContext (options: []) => void Updates the application context on the watch with the updateApplicationContext() WCSession delegate method This has no effect on a CapacitorWatch watch app
isReachable boolean Checks to see if the compaion watch is reachable
updateWatchUI (options: { watchUI: string; }) => void Replaces the current UI on the watch with what is specified here.
updateWatchData (options: { data: { [key: string]: string; }; }) => void Updates the data the watch is using to display variables in text and button fields

capacitor-background-runner's People

Contributors

carlpoole avatar giralte-ionic avatar github-actions[bot] avatar it-mikes avatar jcesarmobile avatar kuldeepdev avatar markemer avatar michaelnjuguna avatar mrnnp avatar mylowmntr avatar pascholda1 avatar steven0351 avatar theproducer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

capacitor-background-runner's Issues

Android receives the notification but does not open the APP when tap at the notification

Only on Android, the device receives the notification but does not open the application when tapping on the notification.
On ios the behavior is as expected

scheduleDate.setSeconds(scheduleDate.getSeconds() + 20);
CapacitorNotifications.schedule([
  {
    id: 100,
    title: "Enterprise Background Runner",
    body: "Received silent push notification",
    scheduleAt: scheduleDate,
  },
]);
resolve();

Seeing errors with a basic attempt at using the plugin

In my background.js file i have 1 event listener defined:

addEventListener("myEvent", (details) => {
  console.log("do something to update the system here:", details);
  details.completed();
});

In my capacitor.config.ts i have Background Runner configured as follows:

BackgroundRunner: {
      label: "com.myappname.background.task",
      src: "background.js",
      event: "myEvent",
      repeat: false,
      interval: 2,
      autoStart: false,
  }

In my React App i am using the @capacitor/app plugin to monitor appStateChange and am triggering myEvent based on the isActive status as follows:

App.addListener('appStateChange', async ({ isActive }) => {
  if(!isActive) {
    await BackgroundRunner.dispatchEvent({
      label: "com.myappname.background.task",
      event: "myEvent",
      details: {foo: 'bar'},
    });
  }
});

Results in the following errors when running the app on an actual iOS device running iOS v16.5.1(c) after putting the app into the background:

starting runner and loading contexts...
2023-07-29 18:39:07.567069-0400 App[39138:2062252] *** Assertion failure in -[CLLocationManager setAllowsBackgroundLocationUpdates:], CLLocationManager.m:1033
2023-07-29 18:39:07.567933-0400 App[39138:2062252] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: !stayUp || CLClientIsBackgroundable(internal->fClient) || _CFMZEnabled()'
*** First throw call stack:
(0x1c0b98cb4 0x1b9c343d0 0x1bb32a56c 0x1cc5b1f28 0x10135d0d8 0x10135ce1c 0x101359274 0x10134e8a0 0x10134b400 0x10134c814 0x101352820 0x10134a600 0x101b7c520 0x101b7e038 0x101b92670 0x101b92e34 0x220918da0 0x220918b7c)
libc++abi: terminating due to uncaught exception of type NSException

What am i missing?

Build Fails on IOS with "Cannot find 'BackgroundRunnerPlugin' in scope"

Following the step-by-step setup instructions, after I add the lines

BackgroundRunnerPlugin.registerBackgroundTask()
BackgroundRunnerPlugin.handleApplicationDidFinishLaunching(launchOptions: launchOptions)

to my AppDelegate.swift file, my IOS build in Xcode quickly fails with the error "Cannot find 'BackgroundRunnerPlugin' in scope".

The only way I have found to successfully build the project is to remove those two lines from my AppDelegate.swift file, which prevents the plugin from running.

What am I missing?

Events can be called but are not scheduled automatically

First of all thanks for the useful plugin!

I tried several hours with the existing documentation but ended up with the following situation:

  • Dispatching events is working / events are executed properly / tested with logging and local notifications
  • Events are not scheduled automatically in the background / no logging / no notifications / no errors

Device: iPhone 11 Pro / iOS 16.6

capacitor.config.js

const config = { plugins: {}, ... }
...
config.plugins.BackgroundRunner = {
  label: 'de.domain.appname.background',
  src: 'background.js',
  event: 'myCustomEvent',
  repeat: true,
  interval: 1,
  autoStart: true
}
...
module.exports = config

background.js

addEventListener('myCustomEvent', (resolve, reject) => {
  console.log('myCustomEvent called')
  try {
    let scheduleDate = new Date();
    scheduleDate.setSeconds(scheduleDate.getSeconds() + 5);

    CapacitorNotifications.schedule([
      {
        id: 100,
        title: "Enterprise Background Runner",
        body: "A test message from the Enterprise Background Runner",
        scheduleAt: scheduleDate,
      },
    ]);

    resolve();
  } catch (err) {
    console.error(err);
    reject(err);
  }
});

App.vue (manuel dispatch)

BackgroundRunner.dispatchEvent({ label: 'de.domain.appname.background', event: 'myCustomEvent', details: {} })

What can I do as a next step for troubleshooting?

Background runner not running when app is in background

Hi,
i am trying to make this work for 3 days now, i am sure that is something small but i can't figure it out.
So here we go:

I am using it on iOS, i made my configurations as in the documentation

  • capacitor.config.ts
plugins: {
        BackgroundRunner: {
            label: 'ro.patrimonium.opla.task',
            src: 'runners/runner.js',
            event: 'checkIn',
            repeat: true,
            interval: 1,
            autoStart: true,
        },
...
}
  • AppDelegate.swift i don't need the notificatons part
import UIKit
import Capacitor
import CapacitorBackgroundRunner
import WonderPush

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        BackgroundRunnerPlugin.registerBackgroundTask()
        BackgroundRunnerPlugin.handleApplicationDidFinishLaunching(launchOptions: launchOptions)
        return true
    }
  • runner.js
addEventListener('checkIn', async (resolve, reject, args) => {
    try {
        if (args && typeof args.identifier !== 'undefined') {
            await CapacitorKV.set('radius', args.radius)
            await CapacitorKV.set('language', args.language)
            await CapacitorKV.set('identifier', args.identifier)
        }
        const push = await CapacitorKV.get('identifier').value;
        const radius = await CapacitorKV.get('radius').value
        const language = await CapacitorKV.get('language').value

        const location = await CapacitorGeolocation.getCurrentPosition();
        // const location = {
        //     latitude: 45.565656,
        //     longitude: 45.5656565,
        // };
        console.log('this is my location ' + JSON.stringify(location))
        const lat = location.latitude
        const lng = location.longitude
        const body = `identifier=${push}&radius=${radius}&language=${language}&lat=${lat}&lng=${lng}`;
        console.log("location lat: " + lat);
        console.log("location lng: " + lng);
        console.log("language: " + language);
        console.log("radius: " + radius);

        const response = await fetch('https://my.endpoint', {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                Authorization: `Bearer token`,
            },
            body: body
        })
        const message = await response.json()
        console.log(JSON.stringify(message))
        if (response.ok) {
            console.log('Fetch is working!')
        } else {
            console.log('There is an error with the fetch function')
        }
        resolve(message)
    } catch (err) {
        console.error(err);
        reject(err);
    }
});
  • Info.plist
	<key>BGTaskSchedulerPermittedIdentifiers</key>
	<array>
		<string>ro.patrimonium.opla.task</string>
	</array>
	<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
	<string>We need to track your location while your device is locked in order to notify you when you are close to a monument.</string>
	<key>NSLocationAlwaysUsageDescription</key>
	<string>$(PRODUCT_NAME) uses location services to track your location in order to notify you when you are close to a monument</string>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>$(PRODUCT_NAME) uses location services to track your location in order to notify you when you are close to a monument</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>fetch</string>
		<string>location</string>
		<string>processing</string>
		<string>remote-notification</string>
	</array>

And now comes the interesting part, if i dispatch the event from the app when it's state is active all is working ok, i am getting the location and it is sent to the dashboard, but when a send the app in background i have 2 strange problems:

  1. I am using xcode to build the app and send it to my device (not simulator) and i am monitoring the logs in xcode, the background runner is getting called but i am getting an error for the location, CapacitorGeolocation.getCurrentPosition() cant get my location, all the permissions are allowed, but when the permission promt comes up i cant choose location always only while using the app, Allow once ...
    If i edit my settings on the phone to have the app location always then it is working.
    So my question is what should i do so the user has the option to allow always the location
  2. If i stop the app execution from xcode and start the app from my device without monitoring it in xcode the background runner event is not triggered at all, i can see the location icon on my phone that pops up for some seconds but no data is sent to dashboard, when i activate the app then all calls are getting triggered and i am getting 4-5 requests to the dashboard.
    I has thinking that CapacitorGeolocation.getCurrentPosition() can't get the location in the 30 seconds so i commented it and added manual location data, but even so the fetch is not getting triggered.

Did you have this problem ? Could somebody help me out please. I am using Ionic 7 with vuejs and capacitor 5

Thank you.

Support for plugins

Hi,

I have a few apps that have a need to perform Bluetooth and File operations (e.g., read data from Bluetooth and write to a file) from a background task.

Do you have any roadmap or future plans to allow further extensibility to the APIs available within the background context here?

I'm watching this with great interest as my current approach when faced with a similar problem in the past on Android was to run a headless WebView within the WorkManager (this was on a cordova base). This looks like a great approach if it can be made more extensible.

Background fetch API not working on Android.

Hello, I have tried fetch API from background JS script. but not working.

addEventListener("fetchTest", async (resolve, reject, args) =>{
    try {
        console.log('fetch start');
        const response  = await fetch('https://jsonplaceholder.typicode.com/todos/1');
        if(response.ok){
            console.log('fetch success');
        }else{
            console.log('fetch failed');
        }
        resolve();
    } catch (error){
        console.log('error caught');
        console.error(error);
        reject();
    }
});
BackgroundRunner.dispatchEvent({
  label: 'com.example.background.task',
  event: 'fetchTest',
  details: {},
});

I have tried this on Android Studio emulator (Android ver 13) but only the messages 'fetch start' and 'error caught' appears on logcat.
Thanks!

Unable to install the module

Hello there !
I know that the module is very recent, but I've been waiting for it for a while, I couldn't resist using it haha
Btw... I can't install it.

Here are the logs:

npm ERR! code 1
npm ERR! path C:\...\node_modules\@capacitor\background-runner
npm ERR! command failed
npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c C:\Users\ADMINI~1\AppData\Local\Temp\postinstall-438df915.cmd
npm ERR! The command syntax is incorrect.

My config :
Win 11
npm 8.15.1
node 16.16.0
capacitor 5

I tried deleting the node_module to get a clean install, or install it using --force, but nothing.
Any ideas?

Thanks again for this plugin though!
Can't wait to use it


EDIT:
after npm update (9.8.0), new error:

npm ERR! code 1
npm ERR! path C:\...\node_modules\@capacitor\background-runner
npm ERR! command failed
npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c mkdir -p $INIT_CWD/android/app/libs/ && cp -f android/src/main/libs/android-js-engine-release.aar $INIT_CWD/android/app/libs/
npm ERR! The command syntax is incorrect.

EDIT 2:
successful installation using WSL !
so there must be a problem in a variable that only works under Unix (I won't go any further)

Background worker always fails on android, when the app is closed

In my background.js I have one simple event listener:

addEventListener("myCustomEvent", (details) => {
  console.log("launched listener");

  let scheduleDate = new Date();

  CapacitorNotifications.schedule([
    {
      id: 1,
      title: "Enterprise Background Runner",
      body: "Received silent push notification",
      scheduleAt: scheduleDate,
    },
  ]);

  console.log(scheduleDate);

  details.completed();
});

and have my capacitor.config.ts defined as follows:

		"BackgroundRunner": {
			"label": "com.williegg.togetherminds.task",
			"src": "background.js",
			"event": "myCustomEvent",
			"repeat": true,
			"interval": 16,
			"autoStart": true
		}

When I open my app, and then navigate back to my phone home screen (with the app still being open in the background), the background worker always finishes succesfully, and the event listener I have defined is fired/executed correctly.

However, when I completely close my app (so my app itself is not running in the background), my background worker always fails. The worker does fire at the interval I have set, however it never actually executes my eventlistener (since the console.log I put at the beginning of the function never gets executed), and instead the android studio console says that the worker failed.

Is there something I am missing or is this a bug with the plugin?

Error getting location on background

Hello I'm testing on android actualli and i got this error:

image

This is my runner :
Here I got the location

image

The function calling the runner:
This function call the runner in background each x seconds and with the obtained loc it sends it to a server (this is actually only for testing)

image

In foreground it works correctly
image

Tested on realme gt neo 2

ios 16.4 "Receive failed with error "Socket is not connected"

Hello, when trying to use the background runner to print a message to the console, I see the following...

2023-07-25 12:53:11.682098-0400 App[1435:66319] [connection] nw_read_request_report [C1] Receive failed with error "Software caused connection abort"
2023-07-25 12:53:11.676307-0400 App[1435:66319] [connection] nw_read_request_report [C6] Receive failed with error "Socket is not connected"
2023-07-25 12:53:11.677610-0400 App[1435:66319] [connection] nw_read_request_report [C2] Receive failed with error "Socket is not connected"
2023-07-25 12:53:11.677770-0400 App[1435:66319] [connection] nw_read_request_report [C2] Receive failed with error "Socket is not connected"
2023-07-25 12:53:11.691876-0400 App[1435:66319] [quic] quic_conn_send_frames_for_key_state_block_invoke [C4.1.1.1:2] [-f824dbb0b13609a5] unable to request outbound data
2023-07-25 12:53:11.696412-0400 App[1435:66319] [quic] quic_conn_send_frames_for_key_state_block_invoke [C8.1.1.1:2] [-f7b53de6ad2c4401] unable to request outbound data
2023-07-25 12:53:11.696524-0400 App[1435:66319] [quic] quic_conn_send_frames_for_key_state_block_invoke [C8.1.1.1:2] [-f7b53de6ad2c4401] unable to request outbound data
2023-07-25 12:53:11.703410-0400 App[1435:66319] [] nw_endpoint_flow_fillout_data_transfer_snapshot copy_info() returned NULL

I am using the following packages...

"@capacitor/app": "^5.0.0",
"@capacitor/background-runner": "^1.0.0-rc.1"

I have setup my AppDelegate.swift, and background.js using the example app in this repo as inspiration, creating a simple listener called "heyo" that prints a message to the console, and returns data using details.completed({message: "heyo"});

My capacitor config is the following...

  label: "..."
  src: "background.js",
  event: "heyo",
  repeat: true,
  interval: 3,
  autoStart: false,

With my label included in the info.plist BGTaskSchedulerPermittedIdentifiers.

I am running the app on a physical device running ios 16.4.

Any help to resolve this issue would be greatly appreciated! Thanks so much.

Unable to connect a device using bluetoothle plugin

I am using the Cordova-Bluetooth-plugin, I tried to connect a device using the background runner but couldn't use the window object in my runner.js file facing an error like the window is not defined. Is there any solution?

Missing code / permissions on example-app and app crash on geolocation event

Hi there,

first of all, thanks for this awesome work !
Just give a try to this plugin (and app test) and i found some issues. On background.js there's missing try statement on line 156 and 168, and there's missing permissions for location in the manifest file.

I was able to test the app (on Android device) after this corrections and everything works except the geolocation. Seems that Android needs FINE location permission instead of COARSE as you can see below :
image

Made the correction to allow FINE location permission in order to run this geolocation event but then I experienced app crash... Tested on Huawei P20 lite / Android 9 and Samsung A41 / Android 12 brings me the same result. Running the app mulitple times make eventually the app showing the location result (1 time over a least 15 tests), but not sure why in this case everything works fine ...
You can see below the crash log from android studio when the event fails :
image

Thanks for your help !

install error

C:\Windows\system32\cmd.exe /d /s /c mkdir -p $INIT_CWD/android/app/libs/ && cp -f android/src/main/libs/android-js-engine-release.aar $INIT_CWD/android/app/libs/
npm ERR! The syntax of the command is incorrect.

Background runner plugin: backgroundrunner.dispatchEvent error

Bug Report

Plugin(s)

"@capacitor/background-runner": "^1.0.5",

Capacitor Version

@capacitor/cli: 5.0.5
@capacitor/core: 5.0.5
@capacitor/android: 5.0.5
@capacitor/ios: 5.0.5

Platform(s)

Android, ios

Current Behavior

I'm using capacitor in a ionic+ stencil application

I'm not able to start correctly BackgroundRunner plugin

This is my runner.js location

Screenshot 2023-10-30 alle 14 58 49

this is my capacitor.config.ts

{
  "appId": "com.myapp.app",
  "appName": "MyApp",
  "webDir": "www",
  "server": {
    "androidScheme": "https"
  },
  "ios": {
    "handleApplicationNotifications": false
  },
  "plugins":{
    "BackgroundRunner": {
      "label": "com.myapp.app.check",
      "src": "runner.js",
      "event": "checkIn",
      "repeat": true,
      "interval": 2,
      "autoStart": false
    }
  }
}

And I have this error

Screenshot 2023-10-30 alle 15 01 33

Is a problem cause by a wrong path for the runner.js file or something else?
I'm not able to make it work!

Expected Behavior

Plugin runs correctly

Doubt about use

I'm making an app that needs to update the user location to a server each 15 minutes I would like to know if it is viable to do with this plugin

Android build

FAILURE: Build completed with 9 failures.

1: Task failed with an exception.

  • What went wrong:
    Execution failed for task ':app:checkDebugAarMetadata'.

Could not resolve all dependencies for configuration ':app:debugRuntimeClasspath'.
The project declares repositories, effectively ignoring the repositories you have declared in the settings.
You can figure out how project repositories are declared by configuring your build to fail on project repositories.
See https://docs.gradle.org/7.5/userguide/declaring_repositories.html#sub:fail_build_on_project_repositories for details.
Could not find :android-js-engine-release:.
Required by:
project :app > project :capacitor-background-runner

  • Try:

Run with --info or --debug option to get more log output.
Run with --scan to get full insights.

  • Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:checkDebugAarMetadata'.
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:69)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:327)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:314)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:307)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:293)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:417)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:339)
    at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:96)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:140)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:125)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:39)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:51)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:54)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:43)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:40)
    at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$executeTasks$7(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:247)
    at org.gradle.internal.model.StateTransitionController.lambda$tryTransition$7(StateTransitionController.java:174)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
    at org.gradle.internal.model.StateTransitionController.tryTransition(StateTransitionController.java:174)
    at org.gradle.internal.build.DefaultBuildLifecycleController.executeTasks(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.build.DefaultBuildWorkGraphController$DefaultBuildWorkGraph.runWork(DefaultBuildWorkGraphController.java:156)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:249)
    at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:109)
    at org.gradle.composite.internal.DefaultBuildController.doRun(DefaultBuildController.java:164)
    at org.gradle.composite.internal.DefaultBuildController.access$000(DefaultBuildController.java:45)
    at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.run(DefaultBuildController.java:183)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    Caused by: org.gradle.api.internal.artifacts.configurations.ResolveExceptionWithHints: Could not resolve all dependencies for configuration ':app:debugRuntimeClasspath'.
    The project declares repositories, effectively ignoring the repositories you have declared in the settings.
    You can figure out how project repositories are declared by configuring your build to fail on project repositories.
    See https://docs.gradle.org/7.5/userguide/declaring_repositories.html#sub:fail_build_on_project_repositories for details.
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.failuresWithHint(DefaultConfiguration.java:764)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.rethrowFailure(DefaultConfiguration.java:1518)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$3700(DefaultConfiguration.java:159)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.rethrowFailure(DefaultConfiguration.java:2174)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.visitContents(DefaultConfiguration.java:1496)
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:375)
    at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:119)
    at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:64)
    at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:89)
    at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:333)
    at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:119)
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:375)
    at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:119)
    at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)
    at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:119)
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:375)
    at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:51)
    at org.gradle.internal.execution.fingerprint.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:131)
    at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:322)
    at org.gradle.internal.execution.fingerprint.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:61)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(CaptureStateBeforeExecutionStep.java:193)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$captureExecutionState$1(CaptureStateBeforeExecutionStep.java:141)
    at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionState(CaptureStateBeforeExecutionStep.java:130)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$execute$0(CaptureStateBeforeExecutionStep.java:75)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:75)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:254)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:91)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:56)
    at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32)
    at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21)
    at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
    at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
    at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
    at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:281)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
    at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
    at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
    at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
    at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
    at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:139)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:128)
    at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
    Caused by: org.gradle.api.internal.artifacts.configurations.ResolveExceptionWithHints: Could not resolve all dependencies for configuration ':app:debugRuntimeClasspath'.

    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:69)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:327)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:314)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:307)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:293)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:417)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:339)
    at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:96)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:140)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:125)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:39)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:51)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:54)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:43)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:40)
    at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$executeTasks$7(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:247)
    at org.gradle.internal.model.StateTransitionController.lambda$tryTransition$7(StateTransitionController.java:174)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
    at org.gradle.internal.model.StateTransitionController.tryTransition(StateTransitionController.java:174)
    at org.gradle.internal.build.DefaultBuildLifecycleController.executeTasks(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.build.DefaultBuildWorkGraphController$DefaultBuildWorkGraph.runWork(DefaultBuildWorkGraphController.java:156)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:249)
    at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:109)
    at org.gradle.composite.internal.DefaultBuildController.doRun(DefaultBuildController.java:164)
    at org.gradle.composite.internal.DefaultBuildController.access$000(DefaultBuildController.java:45)
    at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.run(DefaultBuildController.java:183)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find :android-js-engine-release:.
    Required by:
    project :app > project :capacitor-background-runner

==============================================================================

Version 1.1.0 requires Permission ACCESS_BACKGROUND_LOCATION

If we try to publish an internal release in the google play console, we get two errors, smth like that:

Error
Your declaration of permissions for location determination in the background needs to be updated.

Error
This release contains permissions that have not yet been explained in the Play Console. You can update the permissions declarations via the sensitive app permissions. Further information

I guess this comes from the Manifest in the background-runner sources itself.

image

In our own Manifest, we do not add ACCESS_BACKGROUND_LOCATION:

image

We are not in the need of accessing the GPS-Location with the background runner.

Is there a way to take the permission out?

Wrong event triggered

Steps to reproduce the problem using an Android device:

  • Create a single runner listening to two events, let's say A and B. Schedule A with autostart.
  • Dispatch event B within the Ionic app (e.g.: within a button press event).
  • Minimize the Android app

After a while, event B will be triggered again instead of A.

Fetch problem on iOS 17 simulator

I got the following error on iOS simulator:

CapacitorBackgroundRunner/BackgroundRunner.swift:160: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

The problematic code is this:

returnObj.toDictionary().forEach { key, value in. // Line 160
   dict[String(describing: key)] = value
}

My code in the runner.js file:

addEventListener("fetchTest", async (resolve, reject, args) => {
  try {
    const res = await fetch("https://randomuser.me/api/");
    console.log("res???", res);

    resolve('plain text');
  } catch (err) {
    console.error(err);
    reject(err);
  }
});

Scheduled Notifications not appearing on android

I am trying to schedule a local notification on android using a background runner from the docs. However nothing appears in the status bar after the allotted time. The background runner is working as i tested it for setting KV and retrieving it.

within background.js

addEventListener("notiTestEvent", async (resolve, reject, args) => {
    try {
        let scheduleDate = new Date();
        scheduleDate.setSeconds(scheduleDate.getSeconds() + 5);
        CapacitorNotifications.schedule([
            {
                id: 312,
                title: "Enterprise Background Runner",
                body: "A test message from the Enterprise Background Runner",
                scheduleAt: scheduleDate,
            },
        ]);

        resolve();
    } catch (err) {
        console.log("err", err);
        reject(err);
    }
});

function calling event

  const ontestbasic = async () => {
    try {
      await BackgroundRunner.dispatchEvent({
        label: 'com.reacttest.app.task',
        event: 'notiTestEvent',
        details: {}
      });
      setValue('notification scheduled');
    } catch (err) {
      console.log('err', err);
    }
  };

Heres the repo if it helps
https://github.com/RaafatAburashed1/test-react-noti

Could not find :android-js-engine-release

I'm not able to build android after adding this plugin.

I get repeated errors saying:
Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':app:orDebugRuntimeClasspath'.
Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find :android-js-engine-release:.

Environment:
"@capacitor/android": "^5.2.3"
Android Studio Giraffe | 2022.3.1
classpath 'com.android.tools.build:gradle:8.1.0'

Thanks for the help! Looking forward to using this plugin!

Cannot use fetch inside scheduled runner

We've created a scheduled runner:

    BackgroundRunner: {
      label: '<not relevant>',
      src: 'runners/runner.js',
      event: 'handleNotifications',
      repeat: true,
      interval: 1,
      autoStart: true,
    }

inside the event handler we try to use fetch as follows:

  fetch("https://jsonplaceholder.typicode.com/todos/1").then((data) => {
    console.log("fetch ok");
    resolve();
  }).catch((err) => {
    console.log("fetch failed");
    console.log(err);
    reject();
  });

Only the catch block gets executed, but the err parameter is always empty.

This was tested with 2 different devices, both with Android 13 and with proper internet connection.

Could you please provide any advice on how to solve or further diagnose this issue?

Fetch in android not working

Hello,

I spent about two days on version 1.0.5 to fix the call to the fetch function in the event listener that was getting an error and the error was empty.

Hopefully the not LTS version 1.0.6-devxxxxx6000.0 works. I would like to raise the issue and ask: When will the LTS version for that fix come?

and the android-jw-engine-release.aar should be manually copied to android/libs/...

Greetings and thank you for this useful plugin ;)

How to call a function or url from runner.js

Hi,

I want to know how is it possible from runner.js. Call something in Angular/Capacitor application. e.g. if this background task wants to trigger a function in Angular/Capacitor to synchronize data, how can you do that? I tested using fetch to call a URL from the Capacitor app, but it doesn't work! and there is no other connection from runner.js to Capacitor app.

Best regards

Unable to dispatchEvent on android

I am trying to dispatch an event on receiving a notification while the app is closed.

On iOS this is demonstrated by adding BackgroundRunnerPlugin.dispatchEvent(event: "remoteNotification", eventArgs: userInfo) to the AppDelegate on didReceiveRemoteNotification

How is this achieved on android?

Frequent android app crashes when calling dispatchEvent

My android app is constantly crashing when I call dispatchEvent. It doesn't crash every time. It's about 70% fail. I can't speak for iOS since the company where I work isn't developing an iOS version of our app. I also had to move the android-js-engine-release.aar file in node_modules and move it into ./android/app/libs/android-js-engine-release.aar in order to build the app with background-runner. It seems that the issue is coming from there based on logs in LogCat.

Using: Ionic React
Background runner version: 1.0.5
Android emulator device: Pixel 5
Android emulator OS: Android 11 / API 30
Compile version: 33
Target version: 30

Stack Trace from Android Studio (edited to remove app name):

Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x30 in tid 11397 (DefaultDispatch), pid 11281 (---)
pid-11405                            
pid: 11281, tid: 11397, name: DefaultDispatch  >>> --- <<<
      #00 pc 0000000000095268  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #01 pc 000000000007334c  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #02 pc 0000000000072f88  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #03 pc 000000000007217c  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (JS_DefineProperty+1276) (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #04 pc 0000000000066224  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (JS_NewContextRaw+920) (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #05 pc 00000000000663d4  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (JS_NewContext+20) (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #06 pc 0000000000058a7c  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (Context::Context(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> > const&, JSRuntime*, _JNIEnv*, _jobject*)+152) (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #07 pc 000000000005d6d8  /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/lib/arm64/libandroid_js_engine.so (Java_io_ionic_android_1js_1engine_Context_initContext+324) (BuildId: 23642ddb9d662eef5ccc60ef02e70cf602a60b91)
      #15 pc 0000000000230ca8  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (io.ionic.android_js_engine.Context.<init>+52)
      #18 pc 000000000023168e  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (io.ionic.android_js_engine.Runner.createContext+30)
      #21 pc 00000000000265d2  [anon:dalvik-classes2.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk!classes2.dex] (io.ionic.backgroundrunner.plugin.BackgroundRunner.initContext+182)
      #24 pc 0000000000026c9c  [anon:dalvik-classes2.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk!classes2.dex] (io.ionic.backgroundrunner.plugin.BackgroundRunner.start+36)
      #27 pc 00000000000268aa  [anon:dalvik-classes2.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk!classes2.dex] (io.ionic.backgroundrunner.plugin.BackgroundRunner.execute+134)
      #30 pc 000000000002611a  [anon:dalvik-classes2.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk!classes2.dex] (io.ionic.backgroundrunner.plugin.BackgroundRunnerPlugin$dispatchEvent$1.invokeSuspend+122)
      #33 pc 000000000029d1fa  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith+42)
      #36 pc 00000000002e3558  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlinx.coroutines.DispatchedTask.run+444)
      #39 pc 000000000031f902  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely+2)
      #42 pc 000000000031e5e6  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask+34)
      #45 pc 000000000031e714  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker+56)
      #48 pc 000000000031e6c4  [anon:dalvik-classes.dex extracted in memory from /data/app/~~3YKbJCkYNizHvKvWmPFnow==/---6rPajoauhEUqOtrJbLn0YA==/base.apk] (kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run)
channel '80bf992 --/--.MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!

We're also seeing lots of messages for Memory leak: 32 bytes lost in 1 block from android-js-engine-qjs

Confusing runner src file location

Wondering why
"src": "background.js"
tries to find the file as public/background.js, as shown in observed logs.

It's an Ionic Angular Capacitor project, and I have no such folder in it at all.

Where to place the .js file

Following the documentation it is not clear where to place the .js file. Even with several tests you always get the following error (in an Ionic+Angular project).

`⚡️ To Native -> BackgroundRunner dispatchEvent 83467667

starting runner and loading contexts...

error executing task: runnerError(reason: "source file not found")

ERROR MESSAGE: {"errorMessage":"runnerError(reason: "source file not found")","message":"runnerError(reason: "source file not found")"}

⚡️ [error] - {"errorMessage":"runnerError(reason: "source file not found")","message":"runnerError(reason: "source file not found")"}

⚡️ [log] - Errore in sync, processo terminato. {"errorMessage":"runnerError(reason: "source file not found")"}

To Native Cordova -> BackgroundMode disable BackgroundMode1323252294 ["options": []]`

Documentation on the configuration and use of the plugin is not clear.

The following is not clear:

  1. For both iOS and Android, in what directory exactly is the background.js file supposed to live for each platform respectively?

  2. In capacitor.config.ts is it possible to define multiple events in event: 'myCustomEvent'?
    Or is it that we can only define one event to execute automatically?

  3. If we can indeed define more than one event, how do we configure autoStart, interval and repeat for each?

  4. Does interval: 2 mean 2 minutes? or 2 seconds?

  5. When and where is it appropriate to execute an event that is defined in our runner?
    Is it OK to do this inside the @capacitor/app plugin's appStateChange listener event?
    For example:

App.addListener('appStateChange', async ({isActive}) => {
  if (!isActive) {
    // call the background runner here
    await BackgroundRunner.dispatchEvent({
            label: "com.myapp.background.task",
            event: "myCustomEvent",
            details: {foo: 'bar'},
          });
  }
});

Java Version Mismatch

I installed this plugin into a previously working Ionic / Capacitor project on MacOS, and it appeared to install fine.
However, after install, when I try to build the project for Android, I get the following error:

Task :capacitor-background-runner:compileDebugKotlin FAILED
Execution failed for task ':capacitor-background-runner:compileDebugKotlin'.
'compileDebugJavaWithJavac' task (current target is 11) and 'compileDebugKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version.

This Pull Request appears to address the same issue: #14

Bug?T ask :app:checkDebugAarMetadata FAILED

Im trying to build an android app with Android extension in VScode. Now im experiencing this error
image
I have already made a file in android/app/libs/android-js-engine-release.aar also in background-runner folder, all set up in grandle-build modules. I did delete the grandle cache, cleaning build etc..
image
image

any thoughs please?

Badly documented -

Nowhere in the documentation does it explain what interval is (milliseconds or minutes) and does not define the default value if interval is not set. Documentation also does not specify what other parameters can use used to set into capacitor.config.ts for Background.runner.

background task not executing on iOS

Hi there, I have following setup:

Info.plist:

<key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>de.domain.app.background.task</string>
    </array>

AppDelegate:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      
            BackgroundRunnerPlugin.registerBackgroundTask()
            BackgroundRunnerPlugin.handleApplicationDidFinishLaunching(launchOptions: launchOptions)
        return true
    }

capacitor.config.ts

plugins: {
        CapacitorCookies: {
            enabled: true,
        },
        BackgroundRunner: {
            label: 'de.domain.app.background.task',
            src: 'capacitor/background-task.js',
            event: 'myCustomEvent',
            repeat: true,
            interval: 5,
            autoStart: false,
        },

        PushNotifications: {
            "presentationOptions": ["badge", "sound", "alert"]
        }
    }

capacitor/background-task.js

addEventListener('myCustomEvent', (resolve, reject, args) => {
    console.warn('do something to update the system here');
    resolve();
});

addEventListener('myCustomEventWithReturnData', (resolve, reject, args) => {
    try {
        console.log('accepted this data: ' + JSON.stringify(args));

    } catch (err) {
        reject(err);
    }
});

addEventListener('remoteNotification', (resolve, reject, args) => {
    try {
        console.warn('received silent push notification');

        CapacitorNotifications.schedule([
            {
                id: 100,
                title: 'Enterprise Background Runner',
                body: 'Received silent push notification',
            },
        ]);

        resolve();
    } catch (err) {
        reject();
    }
});

I adjusted webpack to copy capacitor/background-task.js into dist folder.

After running cap-sync and compiling application on device I get following ( correct ) log:

Scheduling de.domain.app.background.task

Unfortunately I do not see any other log in Xcode output. Is there any way to log if the background-task related javascript file could have been loaded from capacitor or if there is any errors on that?

It would be helpful to see, if the reference to the javascript file is broken after compiling for "dist" or if there is any other Issues…
Is there any advice you can share with me?

How to acess other capacitor plugins like sqlite in backgroundTasks.

I am currently working on a project where I need to synchronize the local SQLite database of my app with the server. To achieve this, I am considering using a background runner plugin to fecth data from db and make POST requests. But not finding any way to acess sqlite db inside background task and make post requests.

How I can schedule background task?

I am developing a specialized alarm clock for a specific task. I ran into a problem: a task in the background can be executed once every 15 minutes, this is an extremely long period.

I know that the classic android application has an AlarmManager class, can I use this in my capacitor-application?

Plugin stops appStateChange not triggered on android

After installing @capacitor/[email protected] and setting up, any appStateChange listeners no longer get executed on android

Ionic:

Ionic CLI : 7.1.1 (~/.nvm/versions/node/v20.5.0/lib/node_modules/@ionic/cli)

Capacitor:

Capacitor CLI : 5.7.4
@capacitor/android : 5.7.4
@capacitor/core : 5.7.4
@capacitor/ios : 5.7.4

Utility:

cordova-res : not installed globally
native-run : 2.0.1

System:

NodeJS : v20.5.0 (~/.nvm/versions/node/v20.5.0/bin/node)
npm : 10.2.4
OS : macOS Unknown

Example code

  // capacitor.config.ts
  plugins: {
    BackgroundRunner: {
      src: 'runners/background.js',
      autoStart: true,
    },
  },
// background.js
// empty
// App.tsx
useEffect(() => {
  App.addListener('appStateChange', ({ isActive }) => {
    console.log('IS ACTIVE', isActive);
  });
}, []);

After loading app, sending app to background then being to foreground, no events are triggered, no console log has need output

If I then uninstall background runner the exact some code executes as expected

Expected behaviour

appStateChange is triggered as expected on app state change

Some relevant details are missing in the documentation

I had some issues while following the docs and propose to add these relevant details:

The Info.plist in Xcode requires not only the background modes but also the labels as identifiers as information:

Bildschirmfoto 2023-09-20 um 23 19 13

In the AppDelegate.swift file, an import statement must be added:

Bildschirmfoto 2023-09-18 um 21 20 58

If relevant, location usage descriptions must be added to the Info.plist:

Bildschirmfoto 2023-09-18 um 23 10 28

If the event handler return anything with resolve() it must be a proper JS object:

Bildschirmfoto 2023-09-20 um 22 03 04

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.