sveltecapacitorbackground-mode

How to share custom data in Capacitor Background Runner


I'm building my first app with Capacitor 5.4 and Svelte 3.49. The app is going to send notifications based on a remote API, it is fetching periodically an endpoint and based on the last API activity it calculates the date to schedule a LocalNotification and shows a countdown to the user. In foreground this logic works fine and with battery optimization off notifications are firing in time and with the exact frequency on Android emulator and my physical device.

In background and/or with off-screen notifications already scheduled work fine but if user doesn't open the app again there will never be a new scheduled notification. So my goal is to use Capacitor Background Runner with built-in interval which should make the API call and schedule the next notification. I managed to make it work in this way:

capacitor.config.json

  "plugins": {
    ...
    "BackgroundRunner": {
      "label": "com.myproject.background.task",
      "src": "background.js",
      "event": "myCustomEvent",
      "repeat": true,
      "interval": 5,
      "autoStart": true
    },
    ...
  }

public/background.js

addEventListener('myCustomEvent', (resolve, reject, args) => {
    CapacitorNotifications.schedule([
        {
            id: 100,
            title: 'myCustomEvent public',
            body: 'New scheduling available',
            scheduleAt: new Date(Date.now() + 10000),
        },
    ]);
    resolve();
});

The interval isn't accurate but even a random periodical run is enough for my goal. As APIs here are limited and I cant just import my async calculations method, I need to rewrite it, fetch endpoint passing some variables and schedule a new notification using CapacitorNotifications.schedule.

I really like capacitor, it makes it easy to develop apps but it's really hard to find examples of code implementation.

My questions are:

  1. Can I somehow dispatch an event from background.js to my App.svelte so inside App.svelte I can easly run my async fns to recalculate notification schedulings? (tried dispatchEvent without success)
  2. Can I share some data between App and background.js? I need to pass a "username" variable to the endpoint so I need to access it inside background.js to perform the fetch. (I see there is CapacitorKV but I don't understand how to share use/share it.

Thank you!


Solution

  • I finally managed to solve this by using "BackgroundRunner.dispatchEvent" and "CapacitorKV". When we run the UI we can send the event manually with some data, then we can store it with CapacitorKV api so it remains inside our runner.js and we can restore it.

    1. When app is ready, fire manually your custom event passing some details.customData with BackgroundRunner.dispatchEvent
    2. Intercept the event in runner.js and if args.customData is available, save it with CapacitorKV.set('yourKey', JSON.stringify(customData))
    3. When your event fires in background it won't contain args.customData so you can retrive the last one you saved by using JSON.parse(CapacitorKV.get('yourKey'))

    capacitor.config.json

    "plugins": {
        ...
        "BackgroundRunner": {
          "label": "com.myproject.background.task",
          "src": "runner.js",
          "event": "myCustomEventWithReturnData",
          "repeat": true,
          "interval": 5,
          "autoStart": true
        },
        ...
    }
    

    App.svelte UI

    import { Capacitor, Plugins } from '@capacitor/core';
    const { App, BackgroundRunner } = Plugins;
    
    const execBackgroundRunner = () => {
        BackgroundRunner.dispatchEvent({
            label: 'myCustomEventWithReturnData',
            event: 'myCustomEventWithReturnData',
            details: {
                // custom data
                alerts,
            },
        });
    }
    
    if (Capacitor.isNativePlatform()) {
        // runner.js background service on app start and send the data you need
        execBackgroundRunner();
    
        // BONUS: when you resume your app, force runner.js event again if needed
        App.addListener('resume', () => {
            execBackgroundRunner();
        });
    }
    

    runner.js

    addEventListener('myCustomEventWithReturnData', async (resolve, reject, args) => {
        let alerts = [];
        const hasData = !!args && Array.isArray(args.alerts);
        // data sent from UI
        if (hasData) {
            alerts = args.alerts;
            let str = JSON.stringify(args.alerts);
            CapacitorKV.set('alerts', str)
        } else {
            // process called in background without any data
            // check if data was already saved from UI before
            const kvObj = CapacitorKV.get('alerts');
            if (kvObj && kvObj.value) {
                try {
                        alerts = JSON.parse(kvObj.value);
                    } catch (err) {
                        // invalid JSON str
                    }
                }
            }
        }
        CapacitorNotifications.schedule([
            {
                id: 100,
                title: `myCustomEventWithReturnData alerts: ${alerts.length}, has data ? ${hasData ? 'YES' : 'NO'}`,
                body: 'Capacitor background notification',
                scheduleAt: new Date(Date.now() + 5000), // show after 5 seconds
            },
        ]);
        resolve();
    });