Short answer, Yes
You can call a Dart method in background using it's handle key.
1. Register your plugin in the background
Implement a custom application class (override FlutterApplication
)
public class MyApp extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
@Override
public void registerWith(io.flutter.plugin.common.PluginRegistry registry) {
// For apps using FlutterEmbedding v1
GeneratedPluginRegistrant.registerWith(registry);
// App with V2 will initialize plugins automatically, you might need to register your own however
}
}
Remember to register the class in the AndroidManifest
by adding android:name=".MyApp"
to <application>
attributes.
What is embedding v2?
2. Create a setup function as top level function in your flutter code
/// Define this TopLevel or static
void _setup() async {
MethodChannel backgroundChannel = const MethodChannel('flutter_background');
// Setup Flutter state needed for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();
// This is where the magic happens and we handle background events from the
// native portion of the plugin.
backgroundChannel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'handleBackgroundMessage') {
final CallbackHandle handle =
CallbackHandle.fromRawHandle(call.arguments['handle']);
final Function handlerFunction =
PluginUtilities.getCallbackFromHandle(handle);
try {
var dataArg = call.arguments['message'];
if (dataArg == null) {
print('Data received from callback is null');
return;
}
await handlerFunction(dataArg);
} catch (e) {
print('Unable to handle incoming background message.\n$e');
}
}
return Future.value();
});
3. Create a top level callback that will get the background message and calls it
_bgFunction(dynamic message) {
// Message received in background
// Remember, this will be a different isolate. So, no widgets
}
4. Get the handle key of the background function and setup and send it to native via MethodChannel
// dart:ui needed
CallbackHandle setup PluginUtilities.getCallbackHandle(_setup);
CallbackHandle handle PluginUtilities.getCallbackHandle(_bgFunction);
_channel.invokeMethod<bool>(
'handleFunction',
<String, dynamic>{
'handle': handle.toRawHandle(),
'setup': setup.toRawHandle()
},
);
5. Save them into SharedPref in the native side
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
String methodName = call.method
if (methodName == "handleFunction") {
long handle = call.argument("handle");
long setup = call.argument("setup");
// save them
}
}
6. When background is awaken, start a background isolate
FlutterMain.ensureInitializationComplete(context, null)
val appBundlePath = FlutterMain.findAppBundlePath()
val flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(setupHandleYouHadSaved)
FlutterNativeView backgroundFlutterView = FlutterNativeView(context, true)
val args = FlutterRunArguments()
args.bundlePath = appBundlePath
args.entrypoint = flutterCallback.callbackName
args.libraryPath = flutterCallback.callbackLibraryPath
backgroundFlutterView?.runFromBundle(args)
// Initialize your registrant in the app class
pluginRegistrantCallback?.registerWith(backgroundFlutterView?.pluginRegistry)
7. When your plugin is registered, create a background channel and pass it to
val backgroundChannel = MethodChannel(messenger, "pushe_flutter_background")
8. Call the setup method that would call and give the message to you callback
private fun sendBackgroundMessageToExecute(context: Context, message: String) {
if (backgroundChannel == null) {
return
}
val args: MutableMap<String, Any?> = HashMap()
if (backgroundMessageHandle == null) {
backgroundMessageHandle = getMessageHandle(context)
}
args["handle"] = backgroundMessageHandle
args["message"] = message
// The created background channel at step 7
backgroundChannel?.invokeMethod("handleBackgroundMessage", args, null)
}
The sendBackgroundMessageToExecute
will execute the dart _setup
function and pass the message and callback handle. In the step 2, callback will be called.
Note: There are still certain corner cases you may want to consider (for instance thread waiting and ...). Checkout the samples and see the source code.
There are several projects which support background execution when app is started in the background.
FirebaseMessaging
Pushe
WorkManager