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 MethodChannel
s 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:
- (GitHub) Set up a plugin referencing the callback dispatcher on the Dart side using
PluginUtilities.getCallbackHandle
- (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
.
- Implement native code to perform background execution.
- 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
.
- (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.