Flutter: Unable to call a platform channel method from another isolate

Created on 5 Jan 2018  ·  133Comments  ·  Source: flutter/flutter

When I try to invoke a platform channel method from a custom spawned isolate, the app crashes badly (both on iOS and Android). I'm trying to figure out whether this is expected or not.

If it's not, it's probably worth to mention that somewhere.

Anyways I think that this can potentially be a strong limitation. Is there a way to be able to call platform plugins from a secondary isolate?

P5 annoyance crowd gold engine framework passed first triage plugin crash new feature

Most helpful comment

I have various use cases, e.g.:

  • Download and parse a huge chunk of data (dart:convert methods are not async) , then store it to a sqlite db using sqflite plugin (which uses platform bindings);
  • Prefetch and parse data before serving it to the ui;
  • Encrypt/decrypt and read/write data from/to a file (crypto stuff is done through a method channel in order to use platform security libs);

In general it can happen that you use dependencies that declare some public methods which you don't really know if eventually will use platform-specific code to accomplish their stuff (I'm thinking about flutterfire for example); using them imho should be more an implementation detail that can change with time instead of something written in stone.

Your workaround seems ok for the moment and relatively painless to implement, as data is already encoded in a way to be passed to method channel, and for this reason will pass easily also via an isolate port, however my guess is that performances would be suboptimal.

However I got a bit stuck during implementation: can you provide an example of an easy way to invoke a static method on the main isolate from a secondary one?

Thanks

All 133 comments

cc @mravn-google for triage

The engine code dereferences null and crashes when attempting to send a platform message from the secondary isolate. I haven't pinpointed exactly where yet; I get a tombstone, but need to learn how to interpret such a thing.

As a (clumsy) workaround, the secondary isolate could ask the primary one to send the message.

@sroddy Might I ask what you are trying to accomplish with the secondary isolate?

I have various use cases, e.g.:

  • Download and parse a huge chunk of data (dart:convert methods are not async) , then store it to a sqlite db using sqflite plugin (which uses platform bindings);
  • Prefetch and parse data before serving it to the ui;
  • Encrypt/decrypt and read/write data from/to a file (crypto stuff is done through a method channel in order to use platform security libs);

In general it can happen that you use dependencies that declare some public methods which you don't really know if eventually will use platform-specific code to accomplish their stuff (I'm thinking about flutterfire for example); using them imho should be more an implementation detail that can change with time instead of something written in stone.

Your workaround seems ok for the moment and relatively painless to implement, as data is already encoded in a way to be passed to method channel, and for this reason will pass easily also via an isolate port, however my guess is that performances would be suboptimal.

However I got a bit stuck during implementation: can you provide an example of an easy way to invoke a static method on the main isolate from a secondary one?

Thanks

@sroddy Thanks for the providing the background information.

About invoking static methods on the main isolate: it is not directly supported and has to be implemented using ports. This package may provide that functionality, e.g. here. Haven't tried it myself though.

But we should look for a more general solution here, one that works also when platform channel communication is done as part of the implementation of a plugin or other library.

We don't quite have the hooks for this yet, but if we can assume for a moment that you have a known set of channel names that are to be used from the secondary isolate and not the main one, you should be able to generically reconfigure platform message handling yourself for those channels, transparently implementing the necessary forwarding of binary messages and replies between your two isolates. Plugins would never know the difference.

Solution sketched below.

Suppose you set up ports M1 and R1 on your main isolate and M2, R2 on your secondary isolate. M for message, R for reply.

  • In your secondary isolate, use BinaryMessages.setMockMessageHandler for each channel to forward messages for the platform to M1 (transparently to the plugin that uses BinaryMessage.send with that channel). Store reply callbacks and configure R2 with a handler that invokes the correct one on receipt of the reply for the message. Configure M2 with a handler that forwards to BinaryMessages.handlePlatformMessage. Implement the reply callback there to forward replies to R1.
  • Symmetrically in your main isolate. Configure M1 with a handler that forwards messages for the platform to BinaryMessages.send. Set a reply callback that forwards replies from the platform to R2. Also call BinaryMessages.setMessageHandler for each of the channels to setup a handler of incoming messages from the platform that forwards to M2. Store the reply callbacks and configure R1 with a handler that invokes the correct one on receipt of a reply from the secondary isolate.

@Hixie I suggest moving to next milestone. A workaround seems to exist. A good solution will require some API design work and iteration.

I got the same problem, any news about this issue?

@mravn-google Do you have any update on this? I ran across the same problem ( I'm in a need of generating e.g. RSA 2048 key pair on the platform side which takes a while on older devices or crypt/decrypt data). I'd rather avoid creating threads or services as that would double my work on the platform side since it is to be implemented for both Android and iOS platforms. Do we have any easy way to run those platform specific operations asynchronously from Dart? Your workaround seems to be fine, however it seems that I have some issues with implementing that as I am quite new to Flutter and Dart (I'm not sure how to configure a handler that forwards messages for an Isolate and so on), sorry about that.

I got the same issue.

I need to generate a PDF on the Android/iOS side which takes a while and the compute(...) method crashes the app if i run call from there.

I got this issue, and now i'm stuck.

I want to read accessToken, from shared preferences, in the background isolate, even though the app is not in the foreground (main isolate may not be running, i'm not very sure).

So, can someone tell me a solution to read persisted data from background isolate.
I'm really in trouble now.

/cc @bkonyi This seems related to something you are working on.

I got the same issue. our product base on CPU-bound. we need to use platform method in isolate. Could you please tell us another way to solve this problem?

@Thomson-Tsui I had a similar problem, and based on a distillation of the work by @bkonyi and his FlutterGeofencing sample I managed to get something to work in an isolate that I am using with flutter_blue and other plugins. Its pretty untested but please give it a try.

I got the same issue, i think it's important

Has anyone solved this problem?

I need to play audio at varying intervals in the background. The simplest way to do that would be to have a separate Isolate managing the audio, but without this feature it will take some really weird work around with ports

I have an assumption that when you run an isolate from dart, the native libraries do not link.

For example, when running flutter code from java, this method is called:

   private native void nativeRunBundleAndSnapshotFromLibrary (
       long nativePlatformViewId,
       @NonNull String [] prioritizedBundlePaths,
       @Nullable String entrypointFunctionName,
       @Nullable String pathToEntrypointFunction,
       @NonNull AssetManager manager
   );

In which all the magic happens)

This is verified by the fact that if you create several FlutterNativeView, and in each run:

  public void runFromBundle (FlutterRunArguments args)

then we get a few "dart isolates"

We also face this issue. We need to collect data from background isolates (e.g. location, noise, etc.) which all rely on platform channels. @mravn-google -- any news on an update to the API which could address this?

It's really a serious problem in tool or media apps.
Platform thread isn't an easy way.

I have also faced the similar issue and stuck right now.

+1 (my project also badly needs platform channels in isolates)

I need to render images in background. It have to be done in an isolate because each image takes a few seconds and blocks ui if done in the main thread. I am stuck at the first native call of dart:ui.PictureRecorder and assume each and every graphical call (which uses native functions) will not work either. Since I am using a lot of functions in the canvas it would be a pain to work with callback ports.

A solution is highly appreciated.

+1 I got this issue

It seems like @mravn-google has stopped working for Google in Aarhus. I wonder if there are anyone else from the Google Flutter team who is looking into this. IMO, this is a major show stopper for serious use of Flutter for building resilient and robust Flutter applications....

Maybe @sethladd can give a status?

+1

any updates on this?

+1

The day you met a bug that you do know how to solve by your own but only wait is the last hope till I become flutter engineer.

We need a more detailed explanation of the workaround for new flutter/dart users. I only started using isolates two days ago, and flutter/dart last week, and it's looking like I'm going to have to use the main thread for some seriously labor-intensive tasks, which makes learning flutter a bad solution. Without usable multithreading, I'm better off learning Kotlin and building my apps twice. I hope y'all can get something together that is intelligible for beginners, I'd really like to be able to justify learning flutter to my employers so I can use it at work.
If not here, perhaps on StackOverflow, I've posted a question here: https://stackoverflow.com/q/57466952/6047611

+1 semoga cepet beres

+1 My project needs platform channels in isolates.

I ran into this same issue and made a package (Isolate Handler) using a workaround similar to the one posted by @mravn-google. However, Isolate Handler does allow you to use channels both from the main isolate alongside other isolates. It supports direct MethodChannel calls from within isolates, but currently EventChannel streams are not supported. I will look into adding support for them if it turns out to be something people need.

One downside of the workaround is the need to supply the isolate handler with channel names. It is not a problem in my project as I am writing my native code myself, but otherwise you have to look through the source code of any plugin you are using to find them.

Another option that was already posted above is FlutterIsolate, which uses a different approach and is also worth looking into.

Hopefully the Flutter devs will provide a full and proper solution to this problem soon.

Edit:

I eventually solved my issue more cleanly by starting the isolate from the native side instead using an approach similar to the Flutter first-party Android Alarm Manager plugin.

The only thing I had to do was convert my native code into plugins so they could be registered with the native application, which was a relatively painless migration. If you use plugins from pub.dev it is even simpler as they should work seamlessly.

+1 any update here ?

+1

Interesting thing I am noticing here:
Several plugins seem to be written with the assumption that the method channel call is passing messages from Dart to a background thread; relatedly, there are some mightily confused people who followed that bg geofencing example and now are wondering how to get back on the main thread...
That plugin code will not cause "jank" if they are performing a short task, but they are by default running on the platform thread, what used to be called "the UI thread" on android; this can absolutely cause gestures to be dropped during high workload, and I understand it would block all other message channels as well.
You then are at the mercy of the plugin author's engineering; only if they moved their heavy work to another thread in the native code will you be running code that waits on method channels; those message channels have to run on the platform thread.
While I do understand the rationale the good chinmaygarde relates here, agreeing that:

  • most platform API calls are fast and often need to be made on the main thread;
  • it makes more sense to account for threads in the native code
  • finding one's way back to the "main thread" in a framework method override is obviously confusing to people used to Android Java

    • I can only imagine how that would multiply when making their first platform-thread-only API calls.

I feel that the resulting architecture opened up a significant anti-pattern as a default for plugin authors; on first glance, I think even the firebase ml plugin uses the platform thread for its processing.
So I wish there was some sort of strong warning that would given to the people doing heavy lifting; perhaps something in the default example app that generates on-screen errors if the message channel calls take longer than ~20 ms to return, perhaps something more in-everyone's-face; honestly, whatever convinces those people making db calls &c. to go get a thread already.

Oi, there's no free lunch, that is for sure...

I ended up implementing heavy workload with the native platform threads using Java and Swift. Found no workaround.

@AndruByrne This is indeed very interesting information and I was unaware of it, as obvious as it now seems in hindsight. I already have one issue related to my plugin where a user was trying to use it as a way to perform a heavy lifting task in the background and it locked up the UI.

I agree that there should be a warning and I will add one to my plugin.

+1 need this

Unfortunately - Flutter is not ready for primetime for it to lack something so very basic yet so very critical. Not all Flutter apps can get away with simple async/await. For the heavy lifting Isolates are essential and plugins that cannot allow platform channel method calls from another Isolate make them pointless and redundant. Xamarin had this built into the framework from day 1 - surely this should be up voted for a solution now.

+1 need this

+1

I need to call a platform channel method from another isolate too!

+1

+1

+1

+1 This is a must have

+1
I have a native SDK that I must call. It would be a nice thing to have something like BackgroundFlutterMethodChannel.

+1

+1

Does anyone have any information on what _crashes badly_ actually describes? I'm working on a proof-of-concept that implements a native library and I'm running into watchdog timeouts (0x8badf00d) and other weird errors when sending messages back and forth via a method channel.

I suspect this is caused by some methods [and results] being invoked from other threads but I have not been able to make any progress in pinpointing the exact issue.

+1

+1

+1

+1

+1 need this

+1 need this also!

I think this issue should be prioritized as at least P2.
The uses of the platform channels that I can think of are

  • To call platform-specific APIs
  • To run platform-specific heavy tasks in the background and report when done

And this is a major blocker.

The flutter_downloader plugin handles background isolate code. I think it's worth looking at it.

i was hoping this to be resolved in the upcoming version, but the bug still remains in 1.12 version!

our client side code is heavily on the other side of channel and is written in kotlin and the app almost stucks for each single http connection

Gooooooooooooooogle?!!!!!!!!!!!!?!?!?!?

This plugin might be helpful flutter_isolate

+1 we need this...come on

+1

The biggest problem for me is that fact the communication between Dart and the native platforms happens on the main thread - sending significant chunks of data is impossible without lagging UI or writing cumbersome code to page data.

+1 need this

writing cumbersome code to page data.

@lukaszciastko what do you mean by writing code to page data. Is there a way to solve lagging UI when sending significant chunks of data??

@YaredTaddese

Depending on the complexity of what your ISOLATE does and the inter-communication between your UI and ISOLATE - It is possible to workaround this issue in your UI code by structuring what data the ISOLATE requires to do its job up front prior to invoking the ISOLATE. I have rich PDF generation in my current Flutter app - a user can select multiple PDF's to generate.

Each can generate from 1 to 10,000 pages . My UI is responsive , when each job completes a TAB is constructed showing the report - the user gets notified when a job completes via a TOAST and the UI does not grind to a halt - the user is unaware that there are services in the background busy generating rich PDF's.

Note, my PDF generation is an ISOLATE. In the ISOLATE -, I also need to download images from CLOUD FIRESTORE and embed in a PDF - this all happens seamlessly.

@MsXam but I can't call a platform-specific function from another isolate...which makes me call the platform-specific function in the main isolate...which leads to a lagging UI

I got the same issue, i think it's very important

@YaredTaddese

Depending on the complexity of what your ISOLATE does and the inter-communication between your UI and ISOLATE - It is possible to workaround this issue in your UI code by structuring what data the ISOLATE requires to do its job up front prior to invoking the ISOLATE. I have rich PDF generation in my current Flutter app - a user can select multiple PDF's to generate.

Each can generate from 1 to 10,000 pages . My UI is responsive , when each job completes a TAB is constructed showing the report - the user gets notified when a job completes via a TOAST and the UI does not grind to a halt - the user is unaware that there are services in the background busy generating rich PDF's.

Note, my PDF generation is an ISOLATE. In the ISOLATE -, I also need to download images from CLOUD FIRESTORE and embed in a PDF - this all happens seamlessly.

Are you calling a platform-specific code?

Has anyone got a workaround for calling platform channels from an isolate ?it defeats the idea to do so from the UI thread.

I am stucked on the same thing.

I am calling a methodchannel with a kotlin code, and getStream.io API, but that is slow as hell, and my Flutter UI freeze, I wanted to add compute to it, but I got the same errors.

How can I fix this?

I just ran across this as well. I am prototyping a new app, and really this issue might be a showstopper for me and cause me to look into other non-Flutter solutions, which will make me sad.

As the volume of responses to this issue indicates, this is a MAJOR problem and pretty much defeats the main use case of isolates in the context of a Flutter app.

I just ran across this as well. I am prototyping a new app, and really this issue might be a showstopper for me and cause me to look into other non-Flutter solutions, which will make me sad.

As the volume of responses to this issue indicates, this is a MAJOR problem and pretty much defeats the main use case of isolates in the context of a Flutter app.

yeah this is a huge issue, need to fix ASAP

I've been learning flutter for 10 days, and I love it so far, but after a while experimenting, you start to come into problems, and see the downsides, and THIS issue, opened since 5 jan 2018, oof...

Please fix this bug.

Hope to support soon.

@jpsarda, @klaszlo8207 Don't hold your breath. It doesn't seem like a bug, it's a limitation of the system. And yes, there *was a solution to the problem, until today: https://pub.dev/packages/isolate_handler. The programmer had to pull the plug on it today because a very recent change in Flutter (only a few days old) made it impossible to follow that route any more.

You have to know, however, that this didn't really mean parallelism. It might have appeared that way and looks are important in mobile apps but the platform code runs on the main thread, anyway, so delegating a long task back from the isolate to the main thread didn't really accomplish anything.

Would be nice to have dart handling the multithreading by default any time you use an async function.

@spiderion Async was never meant to be multithreading, just like in many other platforms. Don't misunderstand me, I won't say the platform channel limitation is not a big problem because it is (my app had been working with the plugin I mentioned up until yesterday and now I have to look for workarounds), but async/await was never about multithreading from the very first day.

@zoechi We would love to know if there is any update on this issue or if there is any plan to fix it?
Thank you.

@spiderion They can't really fix it, it follows from the way isolates work. They have no UI engine behind them, hence there is no platform channel communication. However, there are two plugins to help you out:

  • https://pub.dev/packages/flutter_isolate provides a replacement isolate that can communicate to plugins because it creates its own UI backing (nothing that you see or have to deal with, just technically),

  • https://pub.dev/packages/isolate_handler that we have just modified to rely on the package above because the previous way it used was made impossible by a recent Flutter change. The advantage in using this package rather than flutter_isolate itself is that this adds handling capabilities, you can start several isolates, keep track of them and you don't have to set up your own communication between the isolate and the main thread (something you have to do manually with both the original stock Isolate and FlutterIsolate) because it's abstracted away and readily available.

I use the second with perfect success. Actually, I have been using it for quite some time and when the Flutter breaking change came, I helped its programmer to move on to the new solution just because my app broke, too. :-)

So, I don't think it's reasonable to expect the solution from core, especially that most of the use cases don't call for platform channel communication, so the backing UI engine is not something they would want to add to every isolate. Just use the existing plugins when you need that extra functionality.

Hi @deakjahn thank you for your quick answer.

The solutions you have provided look to be very useful. However, I don't know if it could solve my problem. I currently have a situation in the app where the user creates a story lets say similar to Instagram. The app, in that case, needs to upload on the firebase storage a list of videos and files which might take a very long time to upload (around 15 minutes if the connection is slow) then after the files have been uploaded the app needs create a document on the firebase cloud, Firestore. The problem that I encounter is that if the user kills the app while the files are uploading then the complete task is not fully executed.

That's not really the responsibility of the isolate, it just does what you tell it to do. Firebase can resume stopped unloads, as far as I can see, although I never used it myself.

I am trying to run the getBatteryLevel example:
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab
and it crashes everytime I try to run it on an android device. Can someone please help? I am not even sure how to check the error, since there are no logs and it just keeps on running in an infinite loop and I am using the exact same code.

Labeling this issue for P5 ??? 🥵

Please, check out the earlier comments. It seems very unlikely that this will be changed in the framework because it requires mechanisms that most isolates don't need, so there's no point in adding that overhead. For the cases when you do need it, there are packages to solve it.

Users don't care about the mechanism; how hard it is or not. They just need results. This might be impossible to do. But if the users need it, Flutter should provide it...

I understand that the implementation has limitations and might take tons of time to add this feature to the existing isolate or to create a new API with this feature. I just think this should have a higher priority than P5.

Also, I read all of your comments and it feels to me like you are just promoting the package you created.

No, it wasn't me who created it, but I did accept its ownership a few weeks ago, yes. I don't think that was kept secret in this thread. In addition to that, I mentioned two packages (in a single post, actually, just once) that are both able to provide what you look for and I have no affiliation with the second one other than relying on its excellent work done so far.

If you read it, the only reason I used these in the first place myself was that I needed the very same functionality that you asked for. And my apps have been relying on it for more than a year now, without problems. Besides, becoming more familiar with the inner workings of isolates than an occasional developer just using them would be (plus that I started to work on their counterpart in Flutter Web), makes me think that what I wrote was true: this functionality will not be incorporated in the framework because there are quite a few arguments against its inclusion. Not because it would take tons of time, not at all. It would take relatively little time. Simply because it would add a backend complexity to all isolates than only a few of them would actually need and use.

@deakjahn thanks for these tips, will using isolate_handler help me invoke a method and send data to the dart side whenever I get a notification on the native side? (I'm using twilio native sdks and receiving push notifications on the native side)

If this means you have a patform plugin that you use to communicate through a platform channel, then yes, any of the two packages would help. If it works from regular code, then these packages will make it work from an isolate as well.

@deakjahn Thanks for the quick response! My problem is that I couldn't do it with a regular platform channel, I'm not sure but I think the twilio sdk handles listening to push notifications in a seperate isolate and not on the main thread, and I think that's my problem. I wanted to know if it would work if I invoked the method each time I get a notification using one of the 2 packages ?thanks a lot. (I'm considering switching to native apps if this isn't possible)

I don't even know what Twilio is (OK, I googled it :-) ), so I really can't be sure. But at any rate, if you want to interface any native code, you have to use a platform channel, anyway. Either you use an already existing plugin somebody made for using this Twilio or you author it yourself (in which case you either create practically the same plugin, just don't publish it and refer to it locally), or you simply copy the relevant plugin code into your own app (it won't be an external dependency but other than that, it will have practically the same code, anyway).

And, if you do have a platform channel, than the previous answer applies: if you already can use the platform channel and the plugin from the main thread of a regular Flutter app, then you can use the same from an isolate using any of the two packages.

So this issue exists Since 5. Jan. 2018 and it's still not possible with extra effort. The solutions provided are nice - but there is nothing working on desktop yet. So for anyone who wants to try how well flutter is working on desktop and see if it's possible for a future product will get totally stuck here.
Almost any App needs isolates, because there are always some huge computations, how is it possible that there is nothing happening on such important Api design flaws?

I have added a way out for this, in my plugin System Alert Window

The solution is mentioned here Isolate communication.
Although the example provided talks specific to the system alert window plugin, it can be easily replicated for other plugins. And it requires no other fancy plugins to make it work!

This can still be quite problematic. It means that there is no way to call a plugin directly from a isolate that is running on the background / foreground, because you would have to send a message to the app scope in order to do anything with a plugin. So if your app isn't running there is no way to do anything with a plugin in the backgroud.

So for instance if you would like to do anything with shared preferences in the backgroundMessageHandler of the FCM plugin when the app is closed it will throw a MissingPluginException.

Or if you wan't to open the app with a system alert window. The alert window is running on a different isolate. So you would have to run the code within your app scope. This is problematic, because the app is currently closed.

And there are probably many other senarios where this is a huge problem.

@michael-ottink the scenarios you describe are precisely the ones where there shouldn't be a problem. Plugins that execute code in the background generally instantiate a whole new FlutterEngine with its own plugin registry and the isolate that runs within it will be able to directly communicate over platform channels to those plugins.

Incidentally, that means that if you have a need for an isolate to use plugins, the easy way is to just wrap that isolate in a FlutterEngine and it will magically inherit those powers. This is what the flutter_isolate package does for you on Android and iOS (although I agree it would be better to have a proper fix rather than a workaround. The workarounds available today have bottlenecks, overheads or otherwise may be under-maintained or lack support across all platforms.)

@ryanheise so flutter_isolate does it for me? How I can't really tell from the docs. What do I need to do to make it work for my FCM onBackgroundMessage? So I need to spawn a new isolate in my onBackgroundMessageHandler? But I can't use plugins there so how would I use the flutter_isolate then?

Your problem is that firebase_messaging hasn't been updated in a long while and is lagging far behind the latest background execution APIs. If they updated their plugin, you would no longer have this problem. Just to clarify again, the kind of scenario you describe is precisely the one where there shouldn't be a problem because background isolates should already have access to plugins if implemented correctly. the firebase_messaging plugin is not implemented according to the latest APIs and that is why it does not work for you. You can submit a bug report with that project.

I am in dire need for help with this. Is there possibly any way I can do this. I have my first serious dealine tommorow and I just can't figure it out.

Do everything to ensure that plugins are being loaded. I don't know if you have followed the instructions in the README on how to enable background messages, but you need to create a custom Application class that hooks into the initialisation of the plugin's background FlutterNativeView. If you haven't done that, none of the plugins will be available for you to use. If that's still not working, you can try downgrading your project to the old v1-style plugin architecture (and risk breaking other plugins that require v2).

I setup my Application.kt like this

import `in`.jvapps.system_alert_window.SystemAlertWindowPlugin
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

import android.os.Build
import android.app.NotificationManager
import android.app.NotificationChannel

public class Application: FlutterApplication(), PluginRegistrantCallback {

   override fun onCreate() {
     super.onCreate()
     FlutterFirebaseMessagingService.setPluginRegistrant(this)
     createNotificationChannels()
     SystemAlertWindowPlugin.setPluginRegistrant(this)
   }

   override fun registerWith(registry: PluginRegistry) {
     FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
     SystemAlertWindowPlugin.registerWith(registry.registrarFor("in.jvapps.system_alert_window"))
   }

   fun createNotificationChannels() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = "groupChannel"
        val descriptionText = "This is the group channel"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val mChannel = NotificationChannel("59054", name, importance)
        mChannel.description = descriptionText
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
    }
  }
}

Best to post this on the firebase_messaging issues page because it is unrelated to this issue.

I can make a PR, which will have a new 'spawnIsolate' method for 'SchedulerBinding'.

Then it will be possible to call platform methods in this isolate.

This will only help you if you need to call a platform method and get a response.

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';

Future<void> _test(SendPort sendPort) async {
  final dir = await getTemporaryDirectory();
  sendPort.send(dir);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final Completer<Object> completer = Completer<Object>();
  final RawReceivePort receivePort = RawReceivePort(completer.complete);

  final Isolate isolate = await ServicesBinding.instance.spawnIsolate(
    _test,
    receivePort.sendPort,
  );

  print(await completer.future);

  receivePort.close();
  isolate.kill();
}

Log

Performing hot restart...
Syncing files to device Pixel 4...
Restarted application in 722ms.
I/flutter (11705): Directory: '/data/user/0/com.example.bug/cache'

I can make a PR, which will have a new 'spawnIsolate' method for 'SchedulerBinding'.

Then it will be possible to call platform methods in this isolate.

This will only help you if you need to call a platform method and get a response.

SchedulerBinding.instance.spawnIsolate

Any chance to test this? If it fits my needs etc ..

@Nailik
Replace this file (Actual for version 1.17.5)


binding.dart

```dart// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';

import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'system_channels.dart';

/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the LICENSE file stored at the root of the asset
/// bundle, and implements the ext.flutter.evict service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase {
@override
void initInstances() {
super.initInstances();
_instance = this;
_defaultBinaryMessenger = createBinaryMessenger();
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
initLicenses();
SystemChannels.system.setMessageHandler(handleSystemMessage);
}

/// The current [ServicesBinding], if one has been created.
static ServicesBinding get instance => _instance;
static ServicesBinding _instance;

/// The default instance of [BinaryMessenger].
///
/// This is used to send messages from the application to the platform, and
/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
BinaryMessenger _defaultBinaryMessenger;

/// Creates a default [BinaryMessenger] instance that can be used for sending
/// platform messages.
@protected
BinaryMessenger createBinaryMessenger() {
return const _DefaultBinaryMessenger._();
}

/// Handler called for messages received on the [SystemChannels.system]
/// message channel.
///
/// Other bindings may override this to respond to incoming system messages.
@protected
@mustCallSuper
Future handleSystemMessage(Object systemMessage) async { }

/// Adds relevant licenses to the [LicenseRegistry].
///
/// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
/// all the licenses collected by the flutter tool during compilation.
@protected
@mustCallSuper
void initLicenses() {
LicenseRegistry.addLicense(_addLicenses);
}

Stream _addLicenses() async* {
// We use timers here (rather than scheduleTask from the scheduler binding)
// because the services layer can't use the scheduler binding (the scheduler
// binding uses the services layer to manage its lifecycle events). Timers
// are what scheduleTask uses under the hood anyway. The only difference is
// that these will just run next, instead of being prioritized relative to
// the other tasks that might be running. Using _something_ here to break
// this into two parts is important because isolates take a while to copy
// data at the moment, and if we receive the data in the same event loop
// iteration as we send the data to the next isolate, we are definitely
// going to miss frames. Another solution would be to have the work all
// happen in one isolate, and we may go there eventually, but first we are
// going to see if isolate communication can be made cheaper.
// See: https://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh): Remove this complexity once these bugs are fixed.
final Completer rawLicenses = Completer();
Timer.run(() async {
rawLicenses.complete(rootBundle.loadString('LICENSE', cache: false));
});
await rawLicenses.future;
final Completer> parsedLicenses = Completer>();
Timer.run(() async {
parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
});
await parsedLicenses.future;
yield* Stream.fromIterable(await parsedLicenses.future);
}

// This is run in another isolate created by _addLicenses above.
static List _parseLicenses(String rawLicenses) {
final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
final List result = [];
final List licenses = rawLicenses.split(_licenseSeparator);
for (final String license in licenses) {
final int split = license.indexOf('\n\n');
if (split >= 0) {
result.add(LicenseEntryWithLineBreaks(
license.substring(0, split).split('\n'),
license.substring(split + 2),
));
} else {
result.add(LicenseEntryWithLineBreaks(const [], license));
}
}
return result;
}

@override
void initServiceExtensions() {
super.initServiceExtensions();

assert(() {
  registerStringServiceExtension(
    // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
    // the rootBundle cache and cause the entire image cache to be cleared.
    // This is used by hot reload mode to clear out the cache of resources
    // that have changed.
    name: 'evict',
    getter: () async => '',
    setter: (String value) async {
      evict(value);
    },
  );
  return true;
}());

}

/// Called in response to the ext.flutter.evict service extension.
///
/// This is used by the flutter tool during hot reload so that any images
/// that have changed on disk get cleared from caches.
@protected
@mustCallSuper
void evict(String asset) {
rootBundle.evict(asset);
}

Future spawnIsolate(
FutureOr entryPoint(T message),
T message, {
bool paused = false,
bool errorsAreFatal,
SendPort onExit,
SendPort onError,
String debugName,
}) {
assert(
_isMainIsolate,
'Can\'t make multiple levels of isolates',
);

final RawReceivePort messageReceiver = RawReceivePort(
  (Object receivedMessage) async {
    if (receivedMessage is SendPort) {
      receivedMessage.send(
        _IsolateStarter<T>(
          ui.PluginUtilities.getCallbackHandle(entryPoint),
          message,
        ),
      );
    } else if (receivedMessage is _Message) {
      final ByteData result = await defaultBinaryMessenger.send(
        receivedMessage.channel,
        receivedMessage.message,
      );
      receivedMessage.sendPort.send(result);
    }
  },
);
RawReceivePort onExitReceiver;
onExitReceiver = RawReceivePort(
  (Object message) {
    onExit?.send(message);

    onExitReceiver.close();
    messageReceiver.close();
  },
);

return Isolate.spawn(
  _startIsolate,
  messageReceiver.sendPort,
  paused: paused,
  errorsAreFatal: true,
  onExit: onExitReceiver.sendPort,
  onError: onError,
  debugName: debugName,
);

}
}

Future _startIsolate(SendPort sendPort) async {
_sendPortToMainIsolate = sendPort;
_IsolateBinding();

final Completer<_IsolateStarter> completer =
Completer<_IsolateStarter>();

final RawReceivePort receivePort = RawReceivePort(
(Object isolateStarter) {
assert(isolateStarter is _IsolateStarter);
completer.complete(isolateStarter as _IsolateStarter);
},
);

sendPort.send(receivePort.sendPort);

final _IsolateStarter isolateStarter = await completer.future;

receivePort.close();

final Function function =
ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

await function(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

class _IsolateStarter {
_IsolateStarter(this.callbackHandle, this.message);

final ui.CallbackHandle callbackHandle;
final T message;
}

class _Message {
_Message(
this.channel,
this.message,
this.sendPort,
);

final String channel;
final ByteData message;
final SendPort sendPort;
}

class _IsolateBinding extends BindingBase with ServicesBinding {}

/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
const _DefaultBinaryMessenger._();

// Handlers for incoming messages from platform plugins.
// This is static so that this class can have a const constructor.
static final Map _handlers =
{};

// Mock handlers that intercept and respond to outgoing messages.
// This is static so that this class can have a const constructor.
static final Map _mockHandlers =
{};

Future _sendPlatformMessage(String channel, ByteData message) {
final Completer completer = Completer();

if (_isMainIsolate) {
  // ui.window is accessed directly instead of using ServicesBinding.instance.window
  // because this method might be invoked before any binding is initialized.
  // This issue was reported in #27541. It is not ideal to statically access
  // ui.window because the Window may be dependency injected elsewhere with
  // a different instance. However, static access at this location seems to be
  // the least bad option.
  ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
    try {
      completer.complete(reply);
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context:
            ErrorDescription('during a platform message response callback'),
      ));
    }
  });
} else {
  RawReceivePort receivePort;
  receivePort = RawReceivePort(
    (Object message) async {
      assert(message is ByteData);
      completer.complete(message as ByteData);
      receivePort.close();
    },
  );
  _sendPortToMainIsolate.send(
    _Message(channel, message, receivePort.sendPort),
  );
}

return completer.future;

}

@override
Future handlePlatformMessage(
String channel,
ByteData data,
ui.PlatformMessageResponseCallback callback,
) async {
ByteData response;
try {
final MessageHandler handler = _handlers[channel];
if (handler != null) {
response = await handler(data);
} else {
ui.channelBuffers.push(channel, data, callback);
callback = null;
}
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
if (callback != null) {
callback(response);
}
}
}

@override
Future send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}

@override
void setMessageHandler(String channel, MessageHandler handler) {
if (handler == null)
_handlers.remove(channel);
else
_handlers[channel] = handler;
ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
await handlePlatformMessage(channel, data, callback);
});
}

@override
void setMockMessageHandler(String channel, MessageHandler handler) {
if (handler == null)
_mockHandlers.remove(channel);
else
_mockHandlers[channel] = handler;
}
}
```

I have flutter 1.21 so i tried to update all the code to compile.
Now there's one Problem left because _IsolateBinding is missing a lot of implementations of BindingBase Methods...

I get sth like

Error: The non-abstract class '_IsolateBinding' is missing implementations for these members:
 - BindingBase with ServicesBinding.SchedulerBinding.addPersistentFrameCallback

around 30 times.

At the end the console prints

/C:/flutter/packages/flutter/lib/src/services/binding.dart:341:7: Error: 'BindingBase' doesn't implement 'SchedulerBinding' so it can't be used with 'ServicesBinding'.
 - 'BindingBase' is from 'package:flutter/src/foundation/binding.dart' ('/C:/flutter/packages/flutter/lib/src/foundation/binding.dart').
 - 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('/C:/flutter/packages/flutter/lib/src/scheduler/binding.dart').
 - 'ServicesBinding' is from 'package:flutter/src/services/binding.dart' ('/C:/flutter/packages/flutter/lib/src/services/binding.dart').
class _IsolateBinding extends BindingBase with ServicesBinding {}

I'm not 100% sure what the call of _IsolateBinding() in _startIsolate does but without i get the common ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. error.

I tried to fix this but i think my knowledge of mixins is not that good yet.
Any idea how to fix it?


The new file looks like this (so far)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @dart = 2.8

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';

import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'restoration.dart';
import 'system_channels.dart';

/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the `LICENSE` file stored at the root of the asset
/// bundle, and implements the `ext.flutter.evict` service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase, SchedulerBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    _restorationManager = createRestorationManager();
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }

  /// The current [ServicesBinding], if one has been created.
  static ServicesBinding get instance => _instance;
  static ServicesBinding _instance;

  /// The default instance of [BinaryMessenger].
  ///
  /// This is used to send messages from the application to the platform, and
  /// keeps track of which handlers have been registered on each channel so
  /// it may dispatch incoming messages to the registered handler.
  BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
  BinaryMessenger _defaultBinaryMessenger;

  /// Creates a default [BinaryMessenger] instance that can be used for sending
  /// platform messages.
  @protected
  BinaryMessenger createBinaryMessenger() {
    return const _DefaultBinaryMessenger._();
  }

  /// Called when the operating system notifies the application of a memory
  /// pressure situation.
  ///
  /// This method exposes the `memoryPressure` notification from
  /// [SystemChannels.system].
  @protected
  @mustCallSuper
  void handleMemoryPressure() { }

  /// Handler called for messages received on the [SystemChannels.system]
  /// message channel.
  ///
  /// Other bindings may override this to respond to incoming system messages.
  @protected
  @mustCallSuper
  Future<void> handleSystemMessage(Object systemMessage) async {
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'memoryPressure':
        handleMemoryPressure();
        break;
    }
    return;
  }

  /// Adds relevant licenses to the [LicenseRegistry].
  ///
  /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
  /// all the licenses collected by the `flutter` tool during compilation.
  @protected
  @mustCallSuper
  void initLicenses() {
    LicenseRegistry.addLicense(_addLicenses);
  }

  Stream<LicenseEntry> _addLicenses() async* {
    // We use timers here (rather than scheduleTask from the scheduler binding)
    // because the services layer can't use the scheduler binding (the scheduler
    // binding uses the services layer to manage its lifecycle events). Timers
    // are what scheduleTask uses under the hood anyway. The only difference is
    // that these will just run next, instead of being prioritized relative to
    // the other tasks that might be running. Using _something_ here to break
    // this into two parts is important because isolates take a while to copy
    // data at the moment, and if we receive the data in the same event loop
    // iteration as we send the data to the next isolate, we are definitely
    // going to miss frames. Another solution would be to have the work all
    // happen in one isolate, and we may go there eventually, but first we are
    // going to see if isolate communication can be made cheaper.
    // See: https://github.com/dart-lang/sdk/issues/31959
    //      https://github.com/dart-lang/sdk/issues/31960
    // TODO(ianh): Remove this complexity once these bugs are fixed.
    final Completer<String> rawLicenses = Completer<String>();
    scheduleTask(() async {
      rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
    }, Priority.animation);
    await rawLicenses.future;
    final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
    scheduleTask(() async {
      parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
    }, Priority.animation);
    await parsedLicenses.future;
    yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
  }

  // This is run in another isolate created by _addLicenses above.
  static List<LicenseEntry> _parseLicenses(String rawLicenses) {
    final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
    final List<LicenseEntry> result = <LicenseEntry>[];
    final List<String> licenses = rawLicenses.split(_licenseSeparator);
    for (final String license in licenses) {
      final int split = license.indexOf('\n\n');
      if (split >= 0) {
        result.add(LicenseEntryWithLineBreaks(
          license.substring(0, split).split('\n'),
          license.substring(split + 2),
        ));
      } else {
        result.add(LicenseEntryWithLineBreaks(const <String>[], license));
      }
    }
    return result;
  }

  @override
  void initServiceExtensions() {
    super.initServiceExtensions();

    assert(() {
      registerStringServiceExtension(
        // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
        // the rootBundle cache and cause the entire image cache to be cleared.
        // This is used by hot reload mode to clear out the cache of resources
        // that have changed.
        name: 'evict',
        getter: () async => '',
        setter: (String value) async {
          evict(value);
        },
      );
      return true;
    }());
  }

  /// Called in response to the `ext.flutter.evict` service extension.
  ///
  /// This is used by the `flutter` tool during hot reload so that any images
  /// that have changed on disk get cleared from caches.
  @protected
  @mustCallSuper
  void evict(String asset) {
    rootBundle.evict(asset);
  }

  Future<Isolate> spawnIsolate<T>(
      FutureOr<void> entryPoint(T message),
      T message, {
        bool paused = false,
        bool errorsAreFatal,
        SendPort onExit,
        SendPort onError,
        String debugName,
      }) {
    assert(
    _isMainIsolate,
    'Can\'t make multiple levels of isolates',
    );

    final RawReceivePort messageReceiver = RawReceivePort(
          (Object receivedMessage) async {
        if (receivedMessage is SendPort) {
          receivedMessage.send(
            _IsolateStarter<T>(
              ui.PluginUtilities.getCallbackHandle(entryPoint),
              message,
            ),
          );
        } else if (receivedMessage is _Message) {
          final ByteData result = await defaultBinaryMessenger.send(
            receivedMessage.channel,
            receivedMessage.message,
          );
          receivedMessage.sendPort.send(result);
        }
      },
    );
    RawReceivePort onExitReceiver;
    onExitReceiver = RawReceivePort(
          (Object message) {
        onExit?.send(message);

        onExitReceiver.close();
        messageReceiver.close();
      },
    );

    return Isolate.spawn(
      _startIsolate,
      messageReceiver.sendPort,
      paused: paused,
      errorsAreFatal: true,
      onExit: onExitReceiver.sendPort,
      onError: onError,
      debugName: debugName,
    );
  }



  // App life cycle

  /// Initializes the [lifecycleState] with the [Window.initialLifecycleState]
  /// from the window.
  ///
  /// Once the [lifecycleState] is populated through any means (including this
  /// method), this method will do nothing. This is because the
  /// [Window.initialLifecycleState] may already be stale and it no longer makes
  /// sense to use the initial state at dart vm startup as the current state
  /// anymore.
  ///
  /// The latest state should be obtained by subscribing to
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  @protected
  void readInitialLifecycleStateFromNativeWindow() {
    if (lifecycleState != null) {
      return;
    }
    final AppLifecycleState state = _parseAppLifecycleMessage(window.initialLifecycleState);
    if (state != null) {
      handleAppLifecycleStateChanged(state);
    }
  }

  Future<String> _handleLifecycleMessage(String message) async {
    handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message));
    return null;
  }

  static AppLifecycleState _parseAppLifecycleMessage(String message) {
    switch (message) {
      case 'AppLifecycleState.paused':
        return AppLifecycleState.paused;
      case 'AppLifecycleState.resumed':
        return AppLifecycleState.resumed;
      case 'AppLifecycleState.inactive':
        return AppLifecycleState.inactive;
      case 'AppLifecycleState.detached':
        return AppLifecycleState.detached;
    }
    return null;
  }

  /// The [RestorationManager] synchronizes the restoration data between
  /// engine and framework.
  ///
  /// See the docs for [RestorationManager] for a discussion of restoration
  /// state and how it is organized in Flutter.
  ///
  /// To use a different [RestorationManager] subclasses can override
  /// [createRestorationManager], which is called to create the instance
  /// returned by this getter.
  RestorationManager get restorationManager => _restorationManager;
  RestorationManager _restorationManager;

  /// Creates the [RestorationManager] instance available via
  /// [restorationManager].
  ///
  /// Can be overriden in subclasses to create a different [RestorationManager].
  @protected
  RestorationManager createRestorationManager() {
    return RestorationManager();
  }
}

Future<void> _startIsolate<T>(SendPort sendPort) async {
  _sendPortToMainIsolate = sendPort;
  _IsolateBinding();

  final Completer<_IsolateStarter<T>> completer =
  Completer<_IsolateStarter<T>>();

  final RawReceivePort receivePort = RawReceivePort(
        (Object isolateStarter) {
      assert(isolateStarter is _IsolateStarter<T>);
      completer.complete(isolateStarter as _IsolateStarter<T>);
    },
  );

  sendPort.send(receivePort.sendPort);

  final _IsolateStarter<T> isolateStarter = await completer.future;

  receivePort.close();

  final Function function =
  ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

  await function(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

class _IsolateStarter<T> {
  _IsolateStarter(this.callbackHandle, this.message);

  final ui.CallbackHandle callbackHandle;
  final T message;
}

class _Message {
  _Message(
      this.channel,
      this.message,
      this.sendPort,
      );

  final String channel;
  final ByteData message;
  final SendPort sendPort;
}

//TODO not working
class _IsolateBinding extends BindingBase with ServicesBinding {}

/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  // Handlers for incoming messages from platform plugins.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _handlers =
  <String, MessageHandler>{};

  // Mock handlers that intercept and respond to outgoing messages.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _mockHandlers =
  <String, MessageHandler>{};

  Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();

    if (_isMainIsolate) {
      // ui.window is accessed directly instead of using ServicesBinding.instance.window
      // because this method might be invoked before any binding is initialized.
      // This issue was reported in #27541. It is not ideal to statically access
      // ui.window because the Window may be dependency injected elsewhere with
      // a different instance. However, static access at this location seems to be
      // the least bad option.
      ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
        try {
          completer.complete(reply);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context:
            ErrorDescription('during a platform message response callback'),
          ));
        }
      });
    } else {
      RawReceivePort receivePort;
      receivePort = RawReceivePort(
            (Object message) async {
          assert(message is ByteData);
          completer.complete(message as ByteData);
          receivePort.close();
        },
      );
      _sendPortToMainIsolate.send(
        _Message(channel, message, receivePort.sendPort),
      );
    }

    return completer.future;
  }

  @override
  Future<void> handlePlatformMessage(
      String channel,
      ByteData data,
      ui.PlatformMessageResponseCallback callback,
      ) async {
    ByteData response;
    try {
      final MessageHandler handler = _handlers[channel];
      if (handler != null) {
        response = await handler(data);
      } else {
        ui.channelBuffers.push(channel, data, callback);
        callback = null;
      }
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context: ErrorDescription('during a platform message callback'),
      ));
    } finally {
      if (callback != null) {
        callback(response);
      }
    }
  }

  @override
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

  @override
  void setMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _handlers.remove(channel);
    else
      _handlers[channel] = handler;
    ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
      await handlePlatformMessage(channel, data, callback);
    });
  }

  @override
  void setMockMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _mockHandlers.remove(channel);
    else
      _mockHandlers[channel] = handler;
  }

  @override
  bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;

  @override
  bool checkMockMessageHandler(String channel, MessageHandler handler) => _mockHandlers[channel] == handler;
}

@Nailik
At the moment, I cannot update flutter to help you

I'm wondering why the other flutter tools implemented by the flutter team (like say devtools) never hit this blocker.
They seem to be fixing bugs only if they get blocked by them.

This is not a severe new feature, it's a framework blocker.
So according to this https://github.com/flutter/flutter/issues/18761#issuecomment-639248761 it should be a P3

I'm wondering why the other flutter tools implemented by the flutter team (like say devtools) never hit this blocker.
They seem to be fixing bugs only if they get blocked by them.

To be honest, because i think the impact of this issue in real world apps is overrated. I'm just watching the issue because it would make things easier, but there are "workarounds". And many apps and plugins in production are using background functionality.

I'm also not sure if some of the +1s just don't quite understand what the issue is about. (Like https://github.com/flutter/flutter/issues/13937#issuecomment-635683123 ). Imo the official documentation for background isolates & headless runtime (and how to manage it) is still a bit lacking and the firebase messaging stuff is a nightmare, but this has nothing to do with the framework, engine 🤷‍♂️

Maybe just stop bashing the flutter team, there is amazing progress, and this is not something which can't be worked around... And if there is a PR, why not just submit it for the flutter team to consider 🤔

There is a problem with implementing this behavior for isolates.

Example:

There are a main isolate and an engine.
If the isolate needs something, it asks the engine.
If the engine needs something, it asks the isolate.

Now our situation is:
There are 2 isolates and an engine.
If the isolate needs something, it asks the engine.
If the engine needs something, what should it do?
Ask both? Then the whose answer to take?
Or ask just one isolate? Then which one?

@hpoul I have to agree to disagree as many people here are beginners to android/ios programming and they chose their first framework for app development to be flutter.

So it's important that someone realises this and fixes or suggests an official workaround with an example for these kind of issues where you're blocked and forced by the framework to do complex "workarounds" as obviously beginners won't know what to learn or even where to start because there's no well explained workarounds in the comments here or anywhere on stackoverflow or other blogs. There's only stuff pointing to some packages which may or may not have implemented this correctly.

People who have tons of experience working with services, background task queues etc., in both android and ios, who can easily work around this are not the target demographic for flutter.

And coming to off topic comments like that I'm sure there are false +1s in every other issue, which doesn't change the order of the issue priorities by much and this issue still remains in top.

Hi @phanirithvij

flutter_isolate was linked above and has been around since February of 2019. It is also not complex. I'll copy the README which seems straightforward:

FlutterIsolate allows creation of an Isolate in flutter that is able to use flutter plugins. It creates the necessary platform specific bits (FlutterBackgroundView on android & FlutterEngine on iOS) to enable the platform channels to work inside an isolate.

| | Android | iOS | Description |
| :--------------- | :----------------: | :------------------: | :-------------------------------- |
| FlutterIsolate.spawn(entryPoint,message) | :white_check_mark: | :white_check_mark: | spawns a new FlutterIsolate |
| flutterIsolate.pause() | :white_check_mark: | :white_check_mark: | pauses a running isolate |
| flutterIsolate.resume() | :white_check_mark: | :white_check_mark: | resumed a paused isoalte |
| flutterIsolate.kill() | :white_check_mark: | :white_check_mark: | kills a an isolate |

I say straightforward since these methods have the same names as the ones in the "original" Isolate class so if you already know how to use isolates from the official documentation, it shouldn't be difficult to understand how to use this as a drop-in replacement, at least for those methods listed above.

(Note: I, too, would like this issue fixed officially for reasons I have already stated in an earlier comment, some of which I share with you, but I also wouldn't call this workaround "complex".)

In response to @nikitadol 's comment, flutter_isolate creates a separate engine for each isolate so it doesn't run into the same problem. Of course, it's not ideal to have to create a new engine per isolate, that is one reason why I'm sure the Flutter team could do a better job. Similarly, the alternative solution of piping all plugin method calls through the main isolate would create a bottleneck that runs counter to the goals of isolates in the first place, so I'm sure the Flutter team could do a better job of that, too.

I agree with those who have said the Flutter team might have higher priority issues to address first, but based on what we have seen before with the plugin architecture, it may also be that fixing this requires a major architectural change, and several other open issues also require a major architectural change, and weighing all of these different requirements could take time before they can even get around to addressing this particular issue.

@ryanheise

flutter_isolate creates a separate engine for each isolate

so I'm sure the Flutter team could do a better job of that, too.

You say the Flutter team can do this, but how?
The questions I asked above remain valid.
If the solution is to create a new engine, then you can simply use your plugin

(Note, flutter_isolate is not my plugin, but since I also needed this feature I decided to contribute to the project.)

What I meant by "a better job" is that it is probably not necessary to spin up ALL of the machinery in a FlutterEngine just to communicate with plugins, but at the moment, the necessary infrastructure is all tied to the engine so that's why currently flutter_isolate has to spin up a whole engine just to get access to the plugins.

(Note, flutter_isolate is not my plugin, but since I also needed this feature I decided to contribute to the project.)

Sorry, I didn't notice that this is a fork

flutter_isolate has to spin up a whole engine just to get access to the plugins.

If you specify that 'is_background_view = true', then the engine does not start rendering - which means that the engine does not start completely

The precise API for running headless is different for iOS and Android v1 and Android v2, but essentially this is what flutter_isolate already does. There's still a difference in overhead between a normal isolate and a FlutterEngine, though, so getting the overheads down further is only something that the Flutter team would be able to do. It's not just an overhead in startup time, it's also an overhead in memory usage.

So you just suggest optimizing the creation of a new engine to work in the background?

Then this probably does not apply to this issue

I'm not suggesting that, I'm just pointing out that the flutter_isolate workaround has this limitation because of the overheads which can't be avoided. I'm not suggesting that the Flutter team should try to make the workaround more efficient, I think that ideally there would be an architectural change to the way that plugins are loaded so that it wouldn't be necessary to instantiate a FlutterEngine.

If the engine is 1, and isolates are greater than 1, then we return to this question:

https://github.com/flutter/flutter/issues/13937#issuecomment-667314232

I'm not trying to actually come up with a solution for this new lightweight architecture, but if you're asking me, I can think of two variations of lightweight solution:

  1. Make method channels a bit like server sockets by making them accept connections from multiple clients. So a method channel in the platform side of the plugin could accept connections from multiple isolates, and then once connected could send messages to the right isolate.
  2. Create a new plugin registry etc. for each isolate BUT within the same FlutterEngine,

if you're asking me

Yes, I ask

could send messages to the right isolate.

I think this is a bad solution since 'to the right isolate' is a relative concept

Create a new plugin registry etc. for each isolate BUT within the same FlutterEngine,

This could be a good option, but only if the engine always registers plugins by itself.

But this only happens if all plugins in the GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330-L344

Creating additional callbacks is not an obvious behavior.

I think this is a bad solution since 'to the right isolate' is a relative concept

It could be a bad solution, but it can't be bad for that reason. The "right isolate" is unambiguous in the same way a server socket handles client connections in an unambiguous way.

This could be a good option, but only if the engine always registers plugins by itself.

Which would be a good thing to ensure always happens. Currently the mechanism is a bit flaky.

It could be a bad solution, but it can't be bad for that reason. The "right isolate" is unambiguous in the same way a server socket handles client connections in an unambiguous way.

Then each plugin must take into account that it can be accessed from different isolates - bad

Yep.

Let me add my two cents to this discussion... Having commercially worked with Flutter for around two years now, the most important thing that I learnt was that Flutter is a UI toolkit. There wouldn't be anything surprising in this statement - it even says that on the main Flutter website - if people didn't forget about this.

Flutter is a UI toolkit, and what it does well is UI. If you're trying to create a CPU-heavy application that relies on expensive data processing or uses complicated algorithms and consumes larga amounts of data, then you should NOT use a UI toolkit for this purpose. Business Logic should not be mixed with the UI.

Of course, with Dart we can do much more than just UI, but let's not forget the main purpose fo this toolkit.

I've tried a number of approaches, including using headless Flutter - which proved to be a total disaster. Not because it doesn't work, but because we have problems explaining to other developers how it works. The maintenance burdon it adds up is just not worth it. Not too mention testing such a solution. The last time I tried, Flutter Driver wouldn't handle it at all.

If your application really does need the processing power, IMHO, you have three options:

  • move your logic to the server;
  • move your logic to the native platform - you can use Kotlin Native for this - and use Event/Method channels to only provide the View Models to your UI (I think that's what OLX did: https://tech.olx.com/fast-prototypes-with-flutter-kotlin-native-d7ce5cfeb5f1);
  • don't use Flutter and look for another framework (Xamarin/Blazor might be a good fit for this as C# offers a more sophisticated multithreading environment than Dart).

All of the above have their shortcomings, but trying to stretch a library/framework to do what it wasn't really designed for isn't ideal either.

Had not the isolate_handler solved this problem?

@hasonguo It uses flutter_isolate in the background, it just adds an extra layer to make handling isolates (any isolates, actually) easier by adding the boilerplate for communication so that you don't need to set it up manually every time. It used a different way of addressing the issue earlier, but that was rendered impossible by changes in Flutter itself, and was changed to rely on flutter_isolate instead.

We're going in circles. Yes, there are solutions, both plugins actually. As Ryan pointed out, there is an overhead involved in making the channel communications possible. More likely than not, it's exactly this overhead that made the Flutter team avoid adding it automatically to the original Isolate, as you don't need the support in many (most?) cases.

Still, it's not as clean a solution as it could get. If the Flutter team would be able to provide channel communication without the overhead, directly and automatically, right out of the box with any basic Isolate, the whole discussion would be moot. However, the opposite also holds true: they are not really pursuing change for the sake of change. If there is a viable solution in a package or plugin, why spend time and add weight to the core?

Create a new plugin registry etc. for each isolate BUT within the same FlutterEngine,

This could be a good option, but only if the engine always registers plugins by itself.

But this only happens if all plugins in the GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330-L344

Creating additional callbacks is not an obvious behavior.

Another improvement to this second idea is to make have plugins loaded lazily in the new isolate. Normally, if a project depends on many plugins, the GeneratedPluginRegistrant will be quite large and depending on how efficient a plugin's initialisation routine is, this may contribute to some of the overheads. Exactly how to implement this is another story. Platform channels aren't owned by plugins, so currently sending a message over a channel can't be used to trigger the platform side of a plugin to be instantiated. So either method channels would need to be associated with plugins, or there would need to be an additional mechanism that the Dart side of a plugin could call on to initialise itself lazily before the first method channel is created, and that mechanism could trigger the instantiation of the platform side of that plugin within the isolate.

@TahaTesser

In your example, the error is not related to this issue

E/flutter ( 7814): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

Hi @nikitadol
Thanks, updated example, it doesn't throw an exception, is that correct? (I never needed to use before)
Can you please provide a complete reproducible minimal code sample
Thank you

@TahaTesser


code sample

import 'package:battery/battery.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Future<void> main() async {
  await printBatteryLevel();
  await compute(printBatteryLevel, null);
}

Future<void> printBatteryLevel([dynamic message]) async {
  WidgetsFlutterBinding.ensureInitialized();
  print(await Battery().batteryLevel);
}



logs

Launching lib/main.dart on Pixel 4 in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Waiting for Pixel 4 to report its views...
Debug service listening on ws://127.0.0.1:50709/-SPs_6AmL2Q=/ws
Syncing files to device Pixel 4...
I/flutter ( 8708): 39
E/flutter ( 8708): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: UI actions are only available on root isolate.
E/flutter ( 8708): #0      Window._nativeSetNeedsReportTimings (dart:ui/window.dart:1003:86)
E/flutter ( 8708): #1      Window.onReportTimings= (dart:ui/window.dart:996:29)
E/flutter ( 8708): #2      SchedulerBinding.addTimingsCallback (package:flutter/src/scheduler/binding.dart:272:14)
E/flutter ( 8708): #3      SchedulerBinding.initInstances (package:flutter/src/scheduler/binding.dart:209:7)
E/flutter ( 8708): #4      ServicesBinding.initInstances (package:flutter/src/services/binding.dart:27:11)
E/flutter ( 8708): #5      PaintingBinding.initInstances (package:flutter/src/painting/binding.dart:23:11)
E/flutter ( 8708): #6      SemanticsBinding.initInstances (package:flutter/src/semantics/binding.dart:24:11)
E/flutter ( 8708): #7      RendererBinding.initInstances (package:flutter/src/rendering/binding.dart:32:11)
E/flutter ( 8708): #8      WidgetsBinding.initInstances (package:flutter/src/widgets/binding.dart:257:11)
E/flutter ( 8708): #9      new BindingBase (package:flutter/src/foundation/binding.dart:59:5)
E/flutter ( 8708): #10     new _WidgetsFlutterBinding&BindingBase&GestureBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #11     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #12     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #13     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #14     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #15     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #16     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #17     new WidgetsFlutterBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #18     WidgetsFlutterBinding.ensureInitialized (package:flutter/src/widgets/binding.dart:1229:7)
E/flutter ( 8708): #19     printBatteryLevel (package:bug/main.dart:11:25)
E/flutter ( 8708): #20     _IsolateConfiguration.apply (package:flutter/src/foundation/_isolates_io.dart:83:34)
E/flutter ( 8708): #21     _spawn.<anonymous closure> (package:flutter/src/foundation/_isolates_io.dart:90:65)
E/flutter ( 8708): #22     Timeline.timeSync (dart:developer/timeline.dart:163:22)
E/flutter ( 8708): #23     _spawn (package:flutter/src/foundation/_isolates_io.dart:87:35)
E/flutter ( 8708): #24     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:304:17)
E/flutter ( 8708): #25     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 8708): 



flutter doctor -v

[✓] Flutter (Channel stable, 1.20.4, on Mac OS X 10.15.6 19G2021, locale en-BY)
    • Flutter version 1.20.4 at /Users/nikitadold/development/flutter
    • Framework revision fba99f6cf9 (2 weeks ago), 2020-09-14 15:32:52 -0700
    • Engine revision d1bc06f032
    • Dart version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
    • Android SDK at /Users/nikitadold/Library/Android/sdk
    • Platform android-30, build-tools 30.0.0
    • Java binary at: /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.0.1, Build version 12A7300
    • CocoaPods version 1.9.3

[!] Android Studio (version 4.0)
    • Android Studio at /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] IntelliJ IDEA Ultimate Edition (version 2020.2.2)
    • IntelliJ at /Users/nikitadold/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
    • Flutter plugin installed
    • Dart plugin version 202.7319.5

@TahaTesser
please see this comment

Platform messages are only supported from the main isolate. [...]

This behavior is _expected_ and has for the label severe: new feature
this issue should be considered as _feature request_ or _proposal_ and not as _bug_

@iapicca

In your example, the error is not related to this issue

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

@iapicca

In your example, the error is not related to this issue

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

you are right mine is not a good example, please ignore it

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yjbanov picture yjbanov  ·  3Comments

collinjackson picture collinjackson  ·  3Comments

xster picture xster  ·  3Comments

eseidelGoogle picture eseidelGoogle  ·  3Comments

shadyaziza picture shadyaziza  ·  3Comments