Open git-elliot opened 1 year ago
Can you provide a reproducible sample project?
@nmfisher clicking on floatingbutton would trigger the code and print the same error but if I replace FlutterIsolate.spawn
with Isolate.spawn
then there is no such error and code works expected.
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter_isolate/flutter_isolate.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
@pragma('vm:entry-point')
Future<void> someFunction(SendPort sendPort) async {
final port = ReceivePort();
sendPort.send(port.sendPort);
await for (int message in port) {
sendPort.send(Counter(message));
}
}
class Counter {
int value;
Counter(this.value);
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final receivePort = ReceivePort();
Future<void> _incrementCounter() async {
setState(() {
_counter++;
});
final isolate =
await FlutterIsolate.spawn(someFunction, receivePort.sendPort);
receivePort.listen((message) {
if (message is SendPort) {
message.send(_counter);
} else if (message is Counter) {
print(message.value);
}
});
Future.delayed(const Duration(seconds: 1), () {
isolate.kill();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
I also encountered the same problem, how to solve it, I see the source code supports object sending, which confuses me
I opened this problem but after some testing I realized that I have the same problem indicated here.
The fact that this issue has been open since April makes me think it hasn't been taken into due consideration.
@nmfisher @rmawatson Any update on this?
@definitelyme as I mentioned in the other linked issue, there's no update as this is fundamental to how we currently spawn isolates. If you explain what you're trying to do in more detail, I might be able to provide a workaround.
Thanks for your reply. Basically i want to send objects to a spawned isolate using SendPort.send()
, but I get the error Unhandled Exception: Invalid argument: is a regular instance: Instance of [Object]
From flutter's SendPort.send()
docs, it says:
If the sender and receiver isolate share the same code (e.g. isolates created via [Isolate.spawn]), the transitive object graph of [message] can contain any object, with the following exceptions:
- Objects with native resources (subclasses of e.g. NativeFieldWrapperClass1). A [Socket] object for example refers internally to objects that have native resources attached and can therefore not be sent.
- [ReceivePort]
- [DynamicLibrary]
- [Finalizable]
- [Finalizer]
- [NativeFinalizer]
- [Pointer]
- [UserTag]
- MirrorReference
Instances of classes that either themselves are marked with @pragma('vm:isolate-unsendable'), extend or implement such classes cannot be sent through the ports.
Apart from those exceptions any object can be sent. Objects that are identified as immutable (e.g. strings) will be shared whereas all other objects will be copied.
(see the last line)
Init a receive port and spawn an isolate with FlutterIsolate.spawn()
passing the SendPort
as message
/// Spawn isolate using the FlutterIsolate plugin
void _spawnFluterIsolatePlugin() async {
/// Create a [ReceivePort] to send/receive messages from [FlutterIsolate] => [Main Isolate]
final ReceivePort receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort, 'main_isolate_port');
await FlutterIsolate.spawn(_isolateTask, receivePort.sendPort);
/// Optional: Listen to messages from [FlutterIsolate] ==> [Main Isolate]
receivePort.listen((message) {
print('This is the message from FlutterIsolate: $message');
});
// Wait 3 seconds before sending a message to the [FlutterIsolate]
Future.delayed(2.seconds, () {
print('Tried to send message');
IsolateNameServer.lookupPortByName('flutter_isolate_port')?.send(const IsolateMsg('Hello World!'));
});
}
/// Spawn isolate using Isolate.spawn
void _spawnNormalIsolate() async {
/// Create a [ReceivePort] to send/receive messages from [FlutterIsolate] => [Main Isolate]
final ReceivePort receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort, 'main_isolate_port');
await Isolate.spawn(_isolateTask, receivePort.sendPort, errorsAreFatal: true, debugName: '_spawnNormalIsolate');
/// Optional: Listen to messages from [FlutterIsolate] ==> [Main Isolate] receivePort.listen((message) { print('This is the message from FlutterIsolate: $message'); });
// Wait 3 seconds before sending a message to the [FlutterIsolate] Future.delayed(2.seconds, () { print('Tried to send message'); IsolateNameServer.lookupPortByName('flutter_isolate_port')?.send(const IsolateMsg('Hello World!')); }); }
@pragma('vm:entry-point') void _isolateTask(SendPort sendPort) async { /// Create a [ReceivePort] to send/receive messages from [Main Isolate] => [FlutterIsolate] final receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort, 'flutter_isolate_port');
/// Listen to messages from [Main Isolate] ==> [FlutterIsolate] receivePort.listen((msg) async { print('Received a message from main isolate: $msg'); }); }
2. Write a simple Dart class:
```dart
class IsolateMsg {
final String name;
const IsolateMsg(this.name);
}
Execute:
void main() {
/// ...Flutter initialization
// Try [_spawnNormalIsolate]
_spawnNormalIsolate(); // This works. Prints: "Received a message from main isolate: Instance of 'IsolateMsg'"
// // Try [_spawnFluterIsolatePlugin]
// _spawnFluterIsolatePlugin(); // Throws the exception: "[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: is a regular instance reachable via : Instance of 'IsolateMsg'"
}
Throws: [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: is a regular instance reachable via : Instance of 'IsolateMsg'
Text should be printed to the console: Received a message from main isolate: Instance of 'IsolateMsg'
What do you ultimately want to send via your SendPort?
I want to send IsolateMsg
as an object via my SendPort
to FlutterIsolate
. This is supported from Flutter 3.0
Can i do this with the FlutterIsolate
plugin?
PS: I don't want to use Map, or List
Yes but your IsolateMsg just contains a string, and since you can send plain strings via SendPort between FlutterIsolates without a problem, I'm assuming the actual class you want to send is more complicated.
For example, could you serialize your IsolateMsg class to json, send as a plain string then deserialize on the receiving end?
Also, what specifically do you need flutter_isolate for that regular isolates in the newer versions of Flutter can't do?
Okay. Even objects like enums etc..
I just want to know why it's possible to do this with isolates created from Isolate.spawn
but not FlutterIsolate.spawn
. And if there'll be a solution in the future.
It's because the current implementation of FlutterIsolate.spawn
spawns an isolate that does not share the same code as the main Flutter isolate (basically using spawnUri under the hood). On the documentation for SendPort.send
you'll see that that means that you can only send certain primitive types.
Updating the current implementation of FlutterIsolate.spawn
to spawn an isolates that shares the same code might be possible. However, I'm reluctant to invest time to explore if that's possible, because the original intended use case for the flutter_isolate
plugin (plugins in background isolates) is now supported by Flutter >= 3.7. The only remaining use case seems to be (a) people who can't upgrade yet, or (b) people who need to use dart:ui
methods on a background isolate.
Can you explain more about why you still need flutter_isolate
, and why you can't achieve what you need to do with simply using Isolate.spawn
in Flutter >= 3.7?
In my use case, I want to listen to some Firestore collections/documents inside the FlutterIsolate.spawn
function. This can not be done using Isolate.spawn
due to Unsupported operation: Background isolates do not support setMessageHandler()
. Based on the received Firestore data, I want to create a lot of different intertwined objects which I would return to the main thread. Serializing all those objects would be a real headache. Creating the objects in the main thread is not a good option as creating them is computationally expensive. I could use another Isolate
for creating the objects, but that's a lot of extra messaging.
but can send the same when using Isolate.spawn from dart library.