“This is the 19th day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”.

The background_fetch plugin can be used to perform background scheduled tasks in the Flutter application.

The following is from the pub of Background_fetch, and the images are from related websites.


background_fetch | Flutter Package (pub.dev)

Flutter background_fetch

Developed by Transistor Software, creator of Flutter Background Geolocation.

Background Fetch is a simple plug-in that wakes up your APP in the Background every 15 minutes or so, providing a short Background run time. The plug-in will run the callbackFn you provided whenever background-FETCH events are triggered.

🆕 Background Fetch now provides a scheduleTask (scheduled task) method to schedule any single triggered task or periodic task.

iOS

  • There is no way to increase the frequency of fetch- events. The plugin has been tuned as frequently as possible, but you’ll never receive events faster than 15 minutes. The operating system automatically suppresses the frequency of background-FETCH events based on the user’s usage mode. For example, if the user has not used the mobile phone for a long time, the fetch-event will be triggered less frequently.
  • scheduleTaskIt’s like it’s only triggered when it’s plugged in.scheduleTaskIs designed for low-priority tasks that do not run as often as expected.
  • The defaultfetchThe task will run quite frequently.
  • ⚠️ iOS will no longer trigger events when the APP stops running – not on iOSstopOnTerminate:falseThat’s the setup.
  • IOS runs tasks for a few days before Apple’s built-in machine learning algorithms get used to them and then starts firing events regularly. Instead of staring at your log waiting for the event to fire, if you’ve simulated the event run, all you need to know is that everything is configured correctly.
  • IOS stops triggering events if the user hasn’t opened an iOS app for a long time.

Android

The Android version of the plugin provides a [Headless] pub.dartlang.org/documentati… (headless) implementation that allows event processing to continue even after the APP terminates.

content

  • The API documentation
  • Installing a plug-in
  • Configuration wizard
  • The sample
  • debugging
  • The Demo application
  • implementation

🔷 The installation

📂 pubspec. Yaml:

dependencies:
  background_fetch: '^ 0.7.0'
Copy the code

Or install the latest version from Git:

dependencies:
  background_fetch:
    git:
      url: https://github.com/transistorsoft/flutter_background_fetch
Copy the code

🔷 Configuration wizard

iOS

Configuration Background “Capabilities

  • Select the root of the project and select the Capabilities TAB. Background Modes and subsequent Modes are enabled.

    •  Background fetch
    •  Background processing (Only if you intend to use BackgroundFetch.scheduleTask)

Configuration Info. Plist

  1. Open theInfo.plistTo addPermitted background task scheduler identifiersThe key.

  1. Add the required id com.transistorsof.fetch

  1. If you want to get throughBackgroundFetch.scheduleTaskperformThe custom Task, you also need to add these custom ids.

    For example, if you want to execute ataskId: 'com.transistorsoft.customtask'Custom tasks, also need to putcom.transistorsoft.customtaskIdentity added to the projectPermitted background task scheduler identifiersIn the.

⚠️ A task id can use any string you want, but now append these identifiers to com.transistorsoft. Prefixes are a good idea. – In the future, the com.transistorsoft prefix may become required.

BackgroundFetch.scheduleTask(TaskConfig(
  taskId: 'com.transistorsoft.customtask',
  delay: 60 * 60 * 1000  / / ms
));
Copy the code

Android

AndroidManifest

Flutter seems to have problems incorporating third-party libraries into Android’s Androidmanifest.xml, specifically the Android :label attribute.

📂 android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.helloworld">

    <application
         tools:replace="android:label"
         android:name="io.flutter.app.FlutterApplication"
         android:label="flutter_background_geolocation_example"
         android:icon="@mipmap/ic_launcher">
</manifest>
Copy the code

In the configuration above, the following two lines are what you need to add:

    xmlns:tools="http://schemas.android.com/tools"
Copy the code
         tools:replace="android:label"
Copy the code

⚠️ raises a build error if the steps above fail

Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed : Attribute application@label value=(hello_world) from AndroidManifest.xml:17:9- 36
    is also present at [tslocationmanager2.133..aar] AndroidManifest.xml:24:18- 50 value=(@string/app_name).
    Suggestion: add 'tools:replace="android:label"' to <application> element at AndroidManifest.xml:15:5- 38:19 to override.
Copy the code

android/gradle.properties

Verify that the application has been migrated to AndroidX.

📂 android/gradle. Properties:

org.gradle.jvmargs=-Xmx1536M
android.enableJetifier=true
android.useAndroidX=true
Copy the code

In the configuration above, the following two lines are what you need to add:

android.enableJetifier=true
android.useAndroidX=true
Copy the code

android/build.gradle

As apps become more complex and introduce various third-party modules, providing “Global Gradle Configuration Properties” will help all modules match the dependent version they request. Background_fetch is aware of these variables and matches the versions of dependencies it can detect.

📂 android/build. Gradle:

buildscript {
+   Ext. Kotlin_version = '1.3.72' // latest
+   ext {
+       compileSdkVersion = 30 // or latest
+       targetSdkVersion = 30 // or latest
+       AppCompatVersion = "1.1.0" // or latest
+   }

    repositories {
        google()
        jcenter()
    }

    dependencies {
+        The classpath 'com. Android. Tools. Build: gradle: 3.3.1' / / Must use 3.3.1 or who. 4. X is fine.
    }
}

allprojects {
    repositories {
        google()
        jcenter()
+       maven {
+           // [required] background_fetch
+           url "${project(':background_fetch').projectDir}/libs"
+       }
    }
}
Copy the code

Lines that begin with a + (plus) sign are the content to add.

android/app/build.gradle

In addition, you need to make use of Global Configuration Properties yourself, replacing hard-coded values with these references in Android /app/build.gradle.

📂 android/app/build. Gradle:

android {
   compileSdkVersion rootProject.ext.compileSdkVersion
    .
    .
    .
    defaultConfig {
        .
        .
        .
       targetSdkVersion rootProject.ext.targetSdkVersion
    }
}
Copy the code

In the configuration above, the following two lines are what you need to add:

   compileSdkVersion rootProject.ext.compileSdkVersion
Copy the code
       targetSdkVersion rootProject.ext.targetSdkVersion
Copy the code

🔷 The sample

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:background_fetch/background_fetch.dart';

Android only If the APP terminates with enableHeadless: true, the Headless Task will run.
void backgroundFetchHeadlessTask(HeadlessTask task) async {
  String taskId = task.taskId;
  bool isTimeout = task.timeout;
  if (isTimeout) {
    // If the task has exceeded the allowed running time, the current processing must be stopped and the task terminated immediately (.finish(taskId))
    print("[BackgroundFetch] Headless task timed-out: $taskId");
    BackgroundFetch.finish(taskId);
    return;
  }  
  print('[BackgroundFetch] Headless event received.');
  // Add your processing here
  BackgroundFetch.finish(taskId);
}

void main() {
  // Tests can be integrated using the Flutter driver extension.
  / / reference for more information: https://flutter.io/testing/
  runApp(new MyApp());
  // Register the task and receive the BackgroundFetch event after the APP terminates.
  // Required: {stopOnTerminate: false, enableHeadless: true}
  BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _enabled = true;
  int _status = 0;
  List<DateTime> _events = [];

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so they are initialized in an asynchronous method
  Future<void> initPlatformState() async {
    / / configuration BackgroundFetch.
    int status = await BackgroundFetch.configure(BackgroundFetchConfig(
        minimumFetchInterval: 15,
        stopOnTerminate: false,
        enableHeadless: true,
        requiresBatteryNotLow: false,
        requiresCharging: false,
        requiresStorageNotLow: false,
        requiresDeviceIdle: false,
        requiredNetworkType: NetworkType.NONE
    ), (String taskId) async {  // <-- event Handler
      // fetch event callback
      print("[BackgroundFetch] Event received $taskId");
      setState(() {
        _events.insert(0.new DateTime.now());
      });
      // Important: you must signal that the task is complete or the OS will keep your APP running in the background for a long time.BackgroundFetch.finish(taskId); },String taskId) async {  // <-- task timeout Handler
      // The task exceeded the allowed running time. You must stop the current processing and terminate the task immediately (.Finish (taskId))
      print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
      BackgroundFetch.finish(taskId);
    });
    print('[BackgroundFetch] configure success: $status');
    setState(() {
      _status = status;
    });        

    // If the component is removed from the component tree while the asynchronous platform message is still being processed, we discard the response instead of calling setState to update the content that is no longer there.
    if(! mounted)return;
  }

  void _onClickEnable(enabled) {
    setState(() {
      _enabled = enabled;
    });
    if (enabled) {
      BackgroundFetch.start().then((int status) {
        print('[BackgroundFetch] start success: $status');
      }).catchError((e) {
        print('[BackgroundFetch] start FAILURE: $e');
      });
    } else {
      BackgroundFetch.stop().then((int status) {
        print('[BackgroundFetch] stop success: $status'); }); }}void _onClickStatus() async {
    int status = await BackgroundFetch.status;
    print('[BackgroundFetch] status: $status');
    setState(() {
      _status = status;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: const Text('BackgroundFetch Example', style: TextStyle(color: Colors.black)),
          backgroundColor: Colors.amberAccent,
          brightness: Brightness.light,
          actions: <Widget>[
            Switch(value: _enabled, onChanged: _onClickEnable),
          ]
        ),
        body: Container(
          color: Colors.black,
          child: new ListView.builder(
              itemCount: _events.length,
              itemBuilder: (BuildContext context, int index) {
                DateTime timestamp = _events[index];
                return InputDecorator(
                    decoration: InputDecoration(
                        contentPadding: EdgeInsets.only(left: 10.0, top: 10.0, bottom: 0.0),
                        labelStyle: TextStyle(color: Colors.amberAccent, fontSize: 20.0),
                        labelText: "[background fetch event]"
                    ),
                    child: new Text(timestamp.toString(), style: TextStyle(color: Colors.white, fontSize: 16.0))); } ), ), bottomNavigationBar: BottomAppBar( child: Row( children: <Widget>[ RaisedButton(onPressed: _onClickStatus, child: Text('Status')),
              Container(child: Text("$_status"), margin: EdgeInsets.only(left: 20.0() [(), (), (); }}Copy the code

Perform a custom task

In addition to the default background-fetch task defined in BackgrondFetch. Configure, you can perform any single run task or periodic task that you own (iOS requires extra configuration). Anyway, all events trigger the callback provided in **BackgroundFetch#configure** :

⚠ ️ iOS:

  • scheduleTaskOn iOS it only runs when the device is powered on.
  • scheduleTaskDesigned on iOS for low-priority tasks such as cleaning cached files –Unreliable for mission-critical purposes.scheduleTaskNever runs as often as you’d like.
  • The defaultfetchEvents are more reliable and fire more frequently.
  • On iOS, when the user terminates the APP,scheduleTaskIt also ends. Not on iOSstopOnTerminate: falseSettings.
// step 1 :(as usual) configure BackgroundFetch
int status = await BackgroundFetch.configure(BackgroundFetchConfig(
  minimumFetchInterval: 15
), (String taskId) async {  // <-- event callback
  // This is a fetch event callback
  print("[BackgroundFetch] taskId: $taskId");

  // Use the switch statement to provide routing for task scheduling.
  switch (taskId) {
    case 'com.transistorsoft.customtask':
      print("Received custom task");
      break;
    default:
      print("Default fetch task");
  }
  // Done, providing the received task IDBackgroundFetch.finish(taskId); },String taskId) async {  // <-- event timeout callback
  // The task has exceeded the allowed running time. You need to stop the current processing and terminate the task immediately (.finish(taskId)).
  print("[BackgroundFetch] TIMEOUT taskId: $taskId");
  BackgroundFetch.finish(taskId);
});


/ / the second step: make a perform 5000 milliseconds from now "com. Transistorsoft. Customtask" the custom of a single task.
BackgroundFetch.scheduleTask(TaskConfig(
  taskId: "com.transistorsoft.customtask",
  delay: 5000  / / < - ms
));
Copy the code

🔷 debugging

iOS

🆕 is available on iOS 13+BGTaskSchedulerSimulation of events

  • At the time of writing of this article (the PUB document), the new task emulator does not run in the emulator, only in the real machine.

  • After running the APP in the XCode, click [| |] button to initialize the breakpoint.

  • Paste the following command on the console (LLDB) :

    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.transistorsoft.fetch"]
    Copy the code
  • Click the [>] button to continue. Task executes, and then provide * * ` BackgroundFetch. Configure ` * * of the callback function will receive events.

Scarlet content: Simulate any registered task by changing the identity.

Simulate a task timeout event

  • Only the new BGTaskScheduler API supports simulating task timeout events. To simulate a task timeout, fetchCallback (fetch callback) must not call BackgroundFetch. Finish (taskId):

    BackgroundFetch.configure(BackgroundFetchConfig(
      minimumFetchInterval: 15
    ), (String taskId) async {  // <-- event callback
      // This is the fetch event callback
      print("[BackgroundFetch] taskId: $taskId"); 
      //BackgroundFetch.finish(taskId); // <-- emulated iOS task timeout disabled. Finish (task Id)},String taskId) async {  // <-- event timeout callback
      // The task has exceeded the allowed running time. You need to stop the current processing and terminate the task immediately (.finish(taskId)).
      print("[BackgroundFetch] TIMEOUT taskId: $taskId");
      BackgroundFetch.finish(taskId);
    });
    Copy the code
  • Now simulate an iOS task timeout as follows, and simulate an event as above

    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.transistorsoft.fetch"]
    Copy the code

The oldBackgroundFetch API

  • Use it in XCodeDebug->Simulate Background FetchSimulate the background FETCH event.
  • It can take hours or even days for iOS to start a continuous background FETCH event schedule. Because iOS schedules fetch events based on the user’s usage patterns. ifSimulate Background FetchWhen you start running, you can confirm that everything is working properly, just wait.

Android

  • View the plug-in log under $abd logcat.

    $ adb logcat *:S flutter:V, TSBackgroundFetch:V
    Copy the code
  • Emulating a background-fetch event (insert

    ) on the device only works in sdK21 + versions.

    $ adb shell cmd jobscheduler run -f <your.application.id> 999
    Copy the code
  • Lower than sdk21, simulate a “Headless” event (use insert

    )

    $ adb shell am broadcast -a <your.application.id>.event.BACKGROUND_FETCH
    Copy the code

The Demo application

Clone repository and open the /example directory in Android Studio.

🔷 implementation

iOS

Implements performFetchWithCompletionHandler trigger a subscription in cordova plug-in custom events.

Android

Android uses two different mechanisms to implement Background Fetch depending on the Android SDK version. LOLLIPOP and later versions use the new JobScheduler, otherwise use the old AlarmManager.

Unlike iOS, the implementation on Android can resume operations after the application terminates (stopOnTerminate: false) or the device restarts (startOnBoot: true)

🔷 license

The MIT license

See the plugin’s pub for details.