“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.
scheduleTask
It’s like it’s only triggered when it’s plugged in.scheduleTask
Is designed for low-priority tasks that do not run as often as expected.- The default
fetch
The task will run quite frequently. - ⚠️ iOS will no longer trigger events when the APP stops running – not on iOS
stopOnTerminate:false
That’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
- Open the
Info.plist
To addPermitted background task scheduler identifiersThe key.
- Add the required id com.transistorsof.fetch
- If you want to get through
BackgroundFetch.scheduleTask
performThe 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.customtask
Identity 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:
scheduleTask
On iOS it only runs when the device is powered on.scheduleTask
Designed on iOS for low-priority tasks such as cleaning cached files –Unreliable for mission-critical purposes.scheduleTask
Never runs as often as you’d like.- The default
fetch
Events are more reliable and fire more frequently. - On iOS, when the user terminates the APP,
scheduleTask
It also ends. Not on iOSstopOnTerminate: false
Settings.
// 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+BGTaskScheduler
Simulation 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 XCode
Debug->Simulate Background Fetch
Simulate 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.