Unable to understand Flutter Isolate workaround for "'Window_sendPlatformMessage' (4 arguments) cannot be found" error

Issue

I’m converting a very simplistic label printing app to Flutter from Swift. A major component of this app is locally cached databases for offline access of products.

While I could download and cache all of the 12,000+ products on the main thread, this is a terrible, terrible solution for such a heavy operation, and I’d really like to avoid that except as a last resort. Even as a last resort, I’d have much more trouble convincing my employer to let me migrate from Swift to Flutter if background threading is not possible for a basic task such as caching online data to a local database.

As a result, I’ve been exploring Isolates. In the process, I ran into the error "'Window_sendPlatformMessage' (4 arguments) cannot be found" whenever I try to cache data using sqflite and path_provider while on an Isolate.

Now I’m reading on github and stackoverflow that Isolates don’t support the use of plugins (possibly packages?) except in a wonky workaround kind of way. I’ve tried a plugin that just crashes my app with a very cryptic stack trace when I try to use it, so it looks like a workaround is the only way.

I’ve just started building with flutter about a week ago and just started on Isolates about two days ago, so my basic understanding of everything is currently shallow. On the flutter repo, there is a comment that seems to outline a workaround for the plugin issue, which I hope will allow me to use path_provider and sqflite to cache the data I’m getting from my web API.

Could a more experienced Flutter developer break down this explanation into baby-bites for me?

edit:

As noted in an answer below, sqflite is already async, and apparently runs on a different thread than the main one, so it looks like my specific situation will be solved by using compute for my dart-pure API call to get JSON data and using regular sqflite for storage. The need for a beginner’s guide to wonky Isolate workarounds remains nonetheless, so I’m leaving the question open.

Solution

Attempt at explanation

I, too, find the wording of the workaround post in that thread very confusing and I can’t really make full sense of it either, however what the poster seems to be suggesting is that you use setMockMessageHandler to intercept data from invokeMethod.

setMockMessageHandler overrides the handler function which would otherwise take the encoded data and pass it on to the native side, allowing you to use your own function. So when you set a mock message handler for a channel you can “hijack” the data and pass it through a SendPort back to your main isolate.

Once you have the message on the main isolate you can then use BinaryMessenger.send to send the data to its intended destination. This is the step that breaks if you’re in an isolate other than the main one, so now you have bypassed that problem.

Once you receive your data back to the main thread you pass it on back to the isolate and decode it there using a custom message handler that you create with BinaryMessages.setMessageHandler.

Workaround package

If you still need it, I made a package (Isolate Handler) using a workaround very similar to the one you are asking about.

The downside of course of any workaround relying on setMockMessageHandler is that you do need to provide it with the channel names that you will be using. You can find these channel names in the source files by you looking for where the MethodChannels are set.

It seems that sqflite uses com.tekartik.sqflite and path_provider uses plugins.flutter.io/path_provider, so in order to use them in an isolate with Isolate Handler you would do the following:

IsolateHandler().spawn(
    entryPoint,
    channels: [
      MethodChannel('com.tekartik.sqflite'),
      MethodChannel('plugins.flutter.io/path_provider'),
    ]
);

void entryPoint(HandledIsolateContext context) {
  final messenger = HandledIsolate.initialize(context);
  // <Your previous isolate entry point here>
}

Update 2019-09-04:

I ended up solving this problem for myself by starting an isolate from the native side instead. I took inspiration from the official Flutter plugin android_alarm_manager. While that plugin is specific to Android and that was enough for my specific project, it is possible to do it in iOS as well as shown in the now-archived location_background_plugin as well as the Medium post referenced below.

A very comprehensive (if a bit overly focused on Geofencing) post by one of the Flutter developers on the process of initiating isolates from the native side is available on Medium.

Summary of steps taken in the post:

  1. (GitHub) Set up a plugin referencing the callback dispatcher on the Dart side using PluginUtilities.getCallbackHandle
  2. (GitHub) Create the callback dispatcher itself, which:
    • Initializes the MethodChannel(s) you want to access.
    • Calls WidgetsFlutterBinding.ensureInitialized() to set up internal state needed for MethodChannel.
    • Calls setMethodCallHandler on the MethodChannel(s) to listen for background events from the native side of your plugin and invokes your callback when events trigger it.
    • Finally alerts the native side plugin that the callback handler is ready for events using invokeMethod.
  3. Implement native code to perform background execution.
  4. Set permissions to allow background execution as a service.
    • Android
      • (GitHub) Register plugin objects with AndroidManifest.xml as service.
      • (GitHub) Create a custom FlutterActivity.
      • (GitHub) Update application field in AndroidManifest.xml, set it to the newly created FlutterActivity.
    • iOS
      • (GitHub). Modify Info.plist to request permissions
      • (GitHub) Set a reference to the application’s plugin registrant from the application’s AppDelegate.
  5. (GitHub) Initialize plugin on Dart side when the application starts (e.g. from main.)

Conclusion

This is by no means a simple solution and I still hope there will be a better one provided by the Flutter team, but it’s a working one at least with no hickups or unexpected behavior.

I would consider this the only solution ready for production at the moment.

Answered By – Krista

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *