New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins do not work for iOS background execution #21925

Open
bkonyi opened this Issue Sep 16, 2018 · 14 comments

Comments

@bkonyi
Contributor

bkonyi commented Sep 16, 2018

Attempting to use a plugin in the context of a background isolate spawned by another plugin will cause failures when making calls over method channels. On Android, the PluginRegistrant interface is used to allow for plugins to register the application's plugins with a FlutterNativeView but no equivalent is available on iOS.

@xtelinco

This comment has been minimized.

xtelinco commented Sep 21, 2018

Hey, I was wondering if you have had any ideas on how to solve this.

At the moment plugins are registered in the AppDelegate didFinishLaunchingWithOptions. However they are only registered within the FlutterViewController and hence the main UI isolate. So the plugin method channels are only known by the UI isolate.

My idea is to provide a new registerHeadlessPlugins method on the AppDelegate to register plugins for the FlutterHeadlessDartRunner (Background Isolate). You could then register any plugins you need for the background isolate. I think that you may well have differing requirements for the plugins required for the Background tasks, and using the same list as the UI isolate seems a little inflexible.

- (void)registerHeadlessPlugins:(NSObject<FlutterPluginRegistry>*)registry {
    [FLTPathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPathProviderPlugin"]];
}

I have created a patch using this idea. It seems to work in my environment, but I am only a beginner when it comes to Flutter so I may well be missing something.

xtelinco/engine@eccfdfa

@bkonyi

This comment has been minimized.

Contributor

bkonyi commented Oct 15, 2018

Also assigning to @dnfield as he's working on updating the iOS APIs which should resolve this.

@dnfield

This comment has been minimized.

Member

dnfield commented Oct 15, 2018

We've had some discussion of this internally. I think at a high level our options are either:

  1. Run plugin code on any isolate at any time. May result in thread-safety issues (e.g. SQLite plugins running on multiple threads trying to lock the same DB file).
  2. Run plugin code on only one isolate at a time. More complicated to coordinate, but thread safe.

There are a few ways we could attack the second option; all of these require starting or "waking up" the main isolate if it's not already running.

  1. Devise a way to check that we're the only running isolate - if so, register whatever plugin we want and run it. If not, "wake up" the main isolate and send a message to it to run the plugin code.
  2. Devise a way to always forward the plugin message to the main isolate (waking it up if necessary).
  3. Don't have any background isolates for "regular" Flutter apps - always have plugins run the main isolate (waking it up if necessary).

I'd lean away from the second option, as I'm not sure how much value we're really left with if we're starting up the machinery for a Dart Isolate just to do our actual work on another one.

@dnfield

This comment has been minimized.

Member

dnfield commented Oct 15, 2018

@xtelinco

This comment has been minimized.

xtelinco commented Oct 15, 2018

Isn't there a third option. Let the plugin developer decide?

Extend the plugin interface to register that it supports multi threaded operation / multi isolate / background operation. Let it provide a delegate to initialise itself for a new isolate. It would then be responsible managing its own thread safety.

@dnfield

This comment has been minimized.

Member

dnfield commented Oct 15, 2018

I think that's a good idea for things going forward, but it doesn't really help with existing plugins today, right?

@matthew-carroll

This comment has been minimized.

Contributor

matthew-carroll commented Oct 15, 2018

@dnfield I really think we need a broad set of use-cases for questions around what we want to support here. As this thread demonstrates, we quickly get into hypothetical technical details that may or may not reflect the needs of developers.

Personally, I really don't want to expand the plugin interface at all if we don't absolutely have to do. This has already created trouble for us in the existing API. So, before we consider expanding API surface, let's collect a canonical set of use-cases that we can use to measure the viability of various recommendations.

@sir-boformer

This comment has been minimized.

Contributor

sir-boformer commented Oct 16, 2018

A typical use case that I'm currently trying to implement:

APNS receives a silent notification with a content update (maybe via firebase_messaging plugin). The content is stored in a sqlite database (using the sqflite plugin) and must be updated by the headless dart code.

@xtelinco

This comment has been minimized.

xtelinco commented Oct 16, 2018

So I think like above, my use case is a pretty common one for flutter developers. Executing something in background without starting up all the UI infrastructure, there by keeping power / processor time to a minimum. This maybe, update sqllite, send a local notification, lookup the application directory, all things that need plugins.

I don't necessarily need do this in a separate isolate, but if I were to use the UI isolate this would need to be started in a way that doesn't start the UI initialisation.

To use the UI isolate we would need to support, background launch - enter at a particular entry point (not main), execute a background task in the UI isolate. However we would still need to be able to initialise the UI isolate properly at a later point by executing main, on the app receiving a viewWillAppear.

I see this as being quite tricky. I think the way to do it would be some sort of messaging, and tracking whether or not the UI isolate had done its main initialisation. So the UI isolate is Run at app launch, but it doesn't do anything until it receives a message to either launch a background task, or initialise the UI.

Whether or not this is simpler than a separate Isolate I'm not sure. However from a developer point of view it is maybe nicer, as they don't have to get their head around isolates at all.

@dnfield dnfield added this to the bucket11 milestone Oct 16, 2018

@perezd

This comment has been minimized.

perezd commented Oct 25, 2018

A typical use case that I'm currently trying to implement:

APNS receives a silent notification with a content update (maybe via firebase_messaging plugin). The content is stored in a sqlite database (using the sqflite plugin) and must be updated by the headless dart code.

This is 100% the use case we need to support ourselves (internal g3 customer).

@dnfield

This comment has been minimized.

Member

dnfield commented Oct 25, 2018

Would you be doing this in a customized Xcode based application, or you'd want to do this from a plugin in a simple Flutter application (where the plugin spawns the headless isolate)?

@sir-boformer

This comment has been minimized.

Contributor

sir-boformer commented Oct 25, 2018

Looking at this again, I'm not sure if there is a realistic chance to run plugins from headless code. Imagine this scenario:

The phone is booted. The Flutter application (AppDelegate, MainActivity) is not running. At this point, the plugins are not registered or registered.

The backend sends a silent push notification.

On Android, the Application is initialized (but not the activity). A service that extends FirebaseMessagingService receives the silent push notification in a (Android) background thread.

On iOS, application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) is called


At this point I wanted to run some dart code that processes the silent notification and makes use of various flutter plugins (shared_preferences and sqflite) to update the database.

If the main isolate was currently running, it would have been beneficial to run the code in the main isolate to prevent concurrency issues and to update the UI. If no main isolate was found, the dart code should have been run in a headless isolate.

The problem I'm seeing is that flutter plugins are not registered at all before the application is launched by the user.

It seems that we have to handle background updates in native code.

@dnfield

This comment has been minimized.

Member

dnfield commented Oct 25, 2018

There's nothing stopping us from registering plugins in the application call on iOS. There's also no reason to think we can't either send a message to the running isolate or spin it if up if it's not running.

@MaikuB

This comment has been minimized.

MaikuB commented Oct 27, 2018

The scenarios I'm trying to support right now is similar to what has been discussed above. When showing a local notification via a plugin i'm maintaining, a callback is invoked that can run headless Dart code (based on the additions that @bkonyi wrote about at https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124) that could then update the application badge (e.g. via https://github.com/g123k/flutter_app_badger).

Custom notification actions may also invoke headless Dart code that uses plugins e.g. remove data from a SQLite db

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment