communication_channels 1.0.1
communication_channels: ^1.0.1 copied to clipboard
This package is used to make the communication process between isolates(services) more maintainable and systematic.
Description #
This package helps you request executing code in different isolate(ex: service, overlay, etc..) with the ability to register data types that you will work with, then send and receive easily.
These different isolates we call Channels.
-
A channel is basically a new isolate the you want to be able to send and receive data from and to it form any other channel.
-
All channels have what I call
communication provider, it is the way that a channel should communicate with. There is aSocketComProviderprovider that works by making a server and communicate through it. There is other types of providers, for example if you useFlutter, then it is preferable to useIsolateComProvider. here it is
abstract class _ChannelNameServer {
const _ChannelNameServer._();
static SendPort? lookupPortByName(String portName) =>
IsolateNameServer.lookupPortByName(portName);
static bool registerPortWithName(SendPort port, String portName) =>
IsolateNameServer.registerPortWithName(port, portName);
static bool removePortNameMapping(String portName) =>
IsolateNameServer.removePortNameMapping(portName);
}
class IsolateSender extends ProviderSender{
final String portName;
const IsolateSender(this.portName) : assert(portName != "");
@override
Future<bool> send(SentChannelMessageType msg) async {
final sendPort = _ChannelNameServer.lookupPortByName(portName);
if(sendPort == null){return false;}
sendPort.send(msg);
return true;
}
@override
List<Object?> get props => [portName];
}
class IsolateComProvider extends ComProvider<IsolateSender>{
String get portName => sender.portName;
@override
final IsolateSender sender;
ReceivePort? _recPort;
IsolateComProvider({
required String portName,
}) : sender = IsolateSender(portName);
@override
Future<Stream<SentChannelMessageType>> getReceiverBroadcastStream() async {
_recPort = ReceivePort();
_ChannelNameServer.registerPortWithName(_recPort!.sendPort, portName);
return _recPort!.asBroadcastStream()
.takeWhile((msg) => msg is SentChannelMessageType,).cast<SentChannelMessageType>();
}
@override
Future<void> closeStreamObj() async {
_ChannelNameServer.removePortNameMapping(portName);
_recPort?.close();
_recPort = null;
}
}
Usage #
- First, you start by defining all the types that you might send or receive.
For more details about this step you can see the example of
easy_serializationpackage.
final customSerializableObjects = <SerializationConfig>[
SerializationConfig<Offset>(
toMarkupObj: (obj) => {
"dx": obj.dx,
"dy": obj.dy,
},
fromMarkupObj: (markup) => Offset(
markup["dx"],
markup["dy"],
),
),
ShapeFillType.values.config,
SerializationConfig.abstract<Shape>(),
SerializationConfig.serializable<Circle>(Circle.fromMarkup),
SerializationConfig.serializable<Rectangle>(Rectangle.fromMarkup),
SerializationConfig.serializable<Square>(Square.fromMarkup),
];
- Secondly, you must define the channels that you will work with.
final mainChannel = ChannelType(
code: 0, debugName: "MAIN",
provider: SocketComProvider(host: InternetAddress.tryParse("127.0.0.1"), port: 5000,),
);
final isolateChannel = ChannelType(
code: 1, debugName: "ISOLATE",
provider: SocketComProvider(host: InternetAddress.tryParse("127.0.0.1"), port: 5001,),
);
/// Pass all channels you have defined here.
final channelsDefinitions = ChannelsDefinitions([mainChannel, isolateChannel]);
- Then you create the
Channel Functionsthat you will work with. A ChannelFunction is basically a regular function(with arguments and return type you define) that you want to be able to call it from anychanneland execute it in any otherchannel. But its arguments and return types should be registered. Here is a channel function:
// here we add the function configration.
final allAvailableChannelFunctions = <ChannelFunctionConfig>[
PrintIsolateHashCode.config,
];
/// This function shows you that [callFunWithArgs] is executed in [isolateChannel].
///
/// It also returns the [hashCode] of [isolateChannel].
class PrintIsolateHashCode extends ChannelFunction<int>{
@override
late final List<Prop> args = [];
PrintIsolateHashCode() : super.to(
toChannel: isolateChannel,
);
@override
TypeAsync<int> callFunWithArgs() async => Isolate.current.hashCode;
//*////////////////////////// DO NOT MODIFY THIS SECTION BY HAND //////////////////////////*//
@override
ChannelFunctionConfig getConfig() => config;
static final config = ChannelFunctionConfig<PrintIsolateHashCode>(
templateFunction: PrintIsolateHashCode._temp,
fromMarkup: PrintIsolateHashCode.fromJson,
);
PrintIsolateHashCode._temp() : super.temp();
PrintIsolateHashCode.fromJson(Map<String, dynamic> json) : super.fromJson(json);
}
-
Note: Creating new
Channel Functionmight seem complicated, but I provide you with snippets(in/example/snippets) you can configure inVS CodeorAndroid Studio, these will help you create them faster. -
Finnaly, we register our
Channel FunctionsandProps, then we can start using ourChannel Functionsexecute them and check and use their results:
Future<void> initChannelCommunications(ChannelType channel) async {
/// Configure registered-types that can be sent throw a channel.
/// This is commented in details in the example of `easy_serialization` Package.
Prop.registerSerializationConfigs(customSerializableObjects);
// Configure channel functions in use.
ChannelFunction.register(allAvailableChannelFunctions);
// Register this channel and initialize it.
await ThisChannel().init(
channelType: channel,
definitions: channelsDefinitions,
);
}
void main() async {
print("Main hashcode ${Isolate.current.hashCode}");
// call at FIRST.
await initChannelCommunications(mainChannel);
await Isolate.spawn(isolateMain, "");
// This waits until the isolate channel is created.
await isolateChannel.waitUntilStart();
/// Any [ChannelFunction] return and [ChannelResult]
/// that contains the data of the type of error(if some error occurs).
final isolateHashCodeRes = await PrintIsolateHashCode().raise();
print("The isolate hashcode is ${isolateHashCodeRes.correctData}");
await Future.delayed(const Duration(seconds: 2));
// Here we deAttach the channel when we finish.
await ThisChannel().deAttach();
}
void isolateMain(String str) async {
print("isolate hashcode ${Isolate.current.hashCode}");
// call at FIRST.
await initChannelCommunications(isolateChannel);
await Future.delayed(const Duration(seconds: 2));
// Here we deAttach the channel when we finish.
await ThisChannel().deAttach();
}
Utils #
-
There are some utility functions that you can use.
-
1- you can
broadcasta channel function to all channels.
await PrintIsolateHashCode().broadcast();
Additional information #
- For a more detailed example you can see
/example/main.dart.