chipweinberger / flutter_blue_plus

Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android, iOS, macOS
Other
791 stars 479 forks source link

[Help]: Is there support for connection in isolates? #683

Closed enricocaliolo closed 1 year ago

enricocaliolo commented 1 year ago

Requirements

Have you checked this problem on the example app?

No

FlutterBluePlus Version

1.12.13

Flutter Version

3.13.1

What OS?

Android

OS Version

Android 10

Bluetooth Module

Esp32

What is your problem?

Trying to connect to a device inside a different isolate results in this error:

'package:flutter/src/services/platform_channel.dart': Failed assertion: line 530 pos 7: '_binaryMessenger != null || BindingBase.debugBindingType() != null'. Cannot set the method call handler before the binary messenger has been initialized. This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() before this or by passing a custom BinaryMessenger instance to MethodChannel().

The suggestion on the message error did not work. I want to ask if the current version supports connection via isolates, as I would need to refactor a bit of code to align my code with it, so knowing beforehand could save me a lot of trouble. Thanks.

Logs

'package:flutter/src/services/platform_channel.dart': Failed assertion: line 530 pos 7: '_binaryMessenger != null || BindingBase.debugBindingType() != null'. Cannot set the method call handler before the binary messenger has been initialized. This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() before this or by passing a custom BinaryMessenger instance to MethodChannel().
chipweinberger commented 1 year ago

I've never tried it! feel free to submit a PR

I would think it would just work.

chipweinberger commented 1 year ago

Are you doing this in your isolate?

void isolateFunction() async {
  // Ensure that the Flutter engine is initialized
  WidgetsFlutterBinding.ensureInitialized();

  // isolate code
}
chipweinberger commented 1 year ago
Screenshot 2023-11-14 at 12 45 05 PM
chipweinberger commented 1 year ago

did you fix it?

enricocaliolo commented 1 year ago

Hi, sorry for the late reply. Using `WidgetsFlutterBinding.ensureInitialized' did not work. It gave the error: ' UI actions are only available on root isolate.' I also am not sure how I would implement a custom BinaryMessenger between the package and my app, so I haven't tested that.

For more context, I have a self-package outside the main app that handle the bluetooth communication between the app and the ESP I am using, as I need it for more than one app. I tried testing with the app example, but this package is far behind, so I would need to update it. This would be a lot of work, as the API changed a lot since I started, and it may won't even work, so I am deciding what to do.

Just tried with the example app (with the current version) to see if it worked, and it doesn't. When I use a function to connect to a device via compute(), it gives me the same error as it did previously.

Future<bool> connect(BluetoothDevice device) async {
  try {
    await device.connect();
    return true;
  } catch (e) {
    print(e);
    return false;
  }
}

try {
          WidgetsFlutterBinding.ensureInitialized();
          result = await compute(connect, lastDevice);
        } catch (e) {
          print('ERRO: $e');
        }

The actual functionality is a lot more complex than that, but it fails on the initial step of the connection. When I don't use isolates, it works as expected, but when I am on a custom isolate, it breaks and give the error mentioned previously.

chipweinberger commented 1 year ago

WidgetsFlutterBinding.ensureInitialized(); should be in the new isolate. Not the current isolate. (however this may not work still: https://github.com/flutter/flutter/issues/10647)

Future<bool> connect(BluetoothDevice device) async {
  WidgetsFlutterBinding.ensureInitialized();
  try {
    await device.connect();
    return true;
  } catch (e) {
    print(e);
    return false;
  }
}

try {
    result = await compute(connect, lastDevice);
  } catch (e) {
    print('ERRO: $e');
  }

second, you should probably use Isolate. spawn(), and not use compute.

chipweinberger commented 1 year ago

see here: https://docs.flutter.dev/platform-integration/platform-channels#using-plugins-and-channels-from-background-isolates

Plugins and channels can be used by any Isolate, but that Isolate has to be a root Isolate (the one created by Flutter) or registered as a background Isolate for a root Isolate.

The following example shows how to register a background Isolate in order to use a plugin from a background Isolate.

import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';

void _isolateMain(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  FlutterBluePlus.startScan();
}

void main() {
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, rootIsolateToken);
}
enricocaliolo commented 1 year ago

WidgetsFlutterBinding.ensureInitialized(); can't be used outside the main isolate, so I ruled that out. Now, I tried setting the custom BinaryMessenger, but it still give me the same error, that I need to use WidgetsFlutterBinding.ensureInitialized(); or passing a custom BinaryMessenger instance to MethodChannel().

Well, I'll probably just update the package and refactor the code, as I delayed it enough, and then see what I can do. I'll probably report it back here in a few days. Thanks for the replies!

chipweinberger commented 1 year ago

you need to use BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);

chipweinberger commented 1 year ago

did you try it?

ichengao commented 1 year ago

it seems caused by _methods.setMethodCallHandler(_methodCallHandler); only can working on main isolate

enricocaliolo commented 1 year ago

Sorry for the late reply. Just updated the lib, but, unfortunately, it still don't work. I am passing the RootIsolateToken and setting BackgroundIsolateBinaryMessenger, but it still gives me the same error. At least in my app, I am not sure if this would work in the example either.

chipweinberger commented 1 year ago

is all of your FBP code in the isolate?

mixing and matching would cause problems i think

enricocaliolo commented 1 year ago

No. It is on a different flutter project though, it may be related to this. I was passing the RootIsolateToken through the classes I use, dunno if this is optimal or not but it was the only way.

The only code I had on isolates was the connection. I need to confirm some characteristics to assert whether the device is a specific-one for my app or not.

When I have time, I can try putting an Isolate in the example app and see if it works, if you have not already, and then we can confirm for sure if this is a problem on my end or something related to the lib itself.

chipweinberger commented 1 year ago

I'm going to close this. To get isolats to work all you need to do:

  1. put all FBP code in isolate
  2. use BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);

if this doesn't work, please open a new issue.

BuzzBumbleBee commented 11 months ago

@chipweinberger im having the same sort of issues. Its down to a flutter limitation in setMethodCallHandler this calls setMessageHandler

https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/platform_channel.dart#L552

This is not supported with background isolates

More info : https://api.flutter.dev/flutter/services/BackgroundIsolateBinaryMessenger/setMessageHandler.html

Im unsure if this is something flutter_blue_plus can do while using setMethodCallHandler to have the platform code call back to flutter

BuzzBumbleBee commented 11 months ago

Relates to this issue raised against core flutter

https://github.com/flutter/flutter/issues/119207

chipweinberger commented 10 months ago

you can use https://pub.dev/packages/flutter_isolate or https://pub.dev/packages/isolate_handler to workaround this

BuzzBumbleBee commented 10 months ago

@chipweinberger does that work ? I read the flutter engine code and all messages from calling methodChannel.invokeMethod always lands the response on the root isolate

You could have the UI isolate forward the message on to your background isolate, but that probably defeats the point.