inter_isolate_event_channel 1.0.2 copy "inter_isolate_event_channel: ^1.0.2" to clipboard
inter_isolate_event_channel: ^1.0.2 copied to clipboard

Flutter plugin for broadcasting events across multiple isolates/engines via native platform channels.

inter_isolate_event_channel #

pub package License: MIT

A Flutter plugin for broadcasting events across multiple isolates/engines through the native layer.

Features #

  • Event Broadcasting: Broadcast events from one isolate/engine to all other isolates/engines
  • Event Type Filtering: Subscribe to specific event types
  • Type Safety: Generic type parameters with compile-time type checking
  • JSON Validation: Automatic payload validation at emit time
  • Efficient Routing: Event routing through native layer (Android/iOS)
  • Memory Safety: Proper resource management to prevent memory leaks
  • Platform Interface Pattern: Well-structured plugin architecture
  • Comprehensive Error Handling: Clear error messages and exceptions

Use Cases #

This plugin is useful in the following scenarios:

  • Multi-Engine Flutter Apps: Multiple Flutter engines within a native app
  • Add-to-App Scenarios: Integrating Flutter into existing native apps
  • Background Isolate Communication: Sharing events between background and UI isolates
  • Real-time Notifications: Instantly propagating events from one screen to all others

Installation #

Add this to your pubspec.yaml:

dependencies:
  inter_isolate_event_channel: ^1.0.1

Then install packages:

flutter pub get

Usage #

Basic Usage #

Emitting Events

import 'package:inter_isolate_event_channel/inter_isolate_event_channel.dart';

// Emit an event
await InterIsolateEventChannel.emit(
  'call.invite',
  {'callerId': 'user123', 'callerName': 'Alice'}
);

Subscribing to Events

import 'package:inter_isolate_event_channel/inter_isolate_event_channel.dart';

// Subscribe to a specific event type with Generic for type safety
final subscription = InterIsolateEventChannel.on<Map<String, dynamic>>('call.invite').listen((payload) {
  // payload is automatically typed as Map<String, dynamic>
  print('Call invite received: ${payload['callerName']}');
  final callerId = payload['callerId']; // Type safe!
});

// Cancel subscription to prevent memory leaks
await subscription.cancel();

Type Safety #

Generic Type Specification

Specify generic types for compile-time type checking:

// Map type
InterIsolateEventChannel.on<Map<String, dynamic>>('user.login').listen((payload) {
  String userId = payload['userId']; // Type safe!
  String name = payload['name'];
});

// String type
InterIsolateEventChannel.on<String>('message.text').listen((message) {
  print(message.toUpperCase()); // String methods available
});

// int type
InterIsolateEventChannel.on<int>('counter.update').listen((count) {
  print(count * 2); // int operations available
});

// List type
InterIsolateEventChannel.on<List<dynamic>>('tags.updated').listen((tags) {
  print(tags.length); // List methods available
});

Type Mismatch Handling

Events with mismatched payload types are automatically skipped:

// String-only listener
InterIsolateEventChannel.on<String>('mixed.event').listen((message) {
  print('Text message: $message');
});

// int-only listener
InterIsolateEventChannel.on<int>('mixed.event').listen((number) {
  print('Number: $number');
});

// Send various types
await InterIsolateEventChannel.emit('mixed.event', 'Hello'); // String listener receives
await InterIsolateEventChannel.emit('mixed.event', 42);       // int listener receives
await InterIsolateEventChannel.emit('mixed.event', {'key': 'value'}); // Both skip

In debug mode, a warning is printed when type mismatches occur.

Advanced Examples #

Real-time Chat Message Broadcasting

// Engine 1: Send message
await InterIsolateEventChannel.emit('chat.message', {
  'roomId': 'room123',
  'message': 'Hello!',
  'sender': 'user456',
  'timestamp': DateTime.now().toIso8601String(),
});

// Engine 2, 3, 4...: Receive message (type safe)
InterIsolateEventChannel.on<Map<String, dynamic>>('chat.message').listen((payload) {
  if (payload['roomId'] == currentRoomId) {
    displayMessage(payload['message'], payload['sender']);
  }
});

State Synchronization

// Broadcast login state change
await InterIsolateEventChannel.emit('auth.login', {
  'userId': 'user789',
  'token': 'jwt_token_here',
});

// Update login state on all screens
InterIsolateEventChannel.on('auth.login').listen((payload) {
  updateAuthState(payload['userId'], payload['token']);
});

// Logout
await InterIsolateEventChannel.emit('auth.logout', null);

How It Works #

┌─────────────┐         ┌──────────────────┐         ┌─────────────┐
│  Isolate A  │────────▶│  Native Layer    │────────▶│  Isolate B  │
│             │  emit() │  (Broadcaster)   │ stream  │             │
└─────────────┘         └──────────────────┘         └─────────────┘
                               │
                               ├────────────────────▶ Isolate C
                               ├────────────────────▶ Isolate D
                               └────────────────────▶ Isolate E
  1. Event Emission: The emit() method sends events to the native layer via MethodChannel
  2. Native Broadcasting: The singleton broadcaster forwards events to all registered EventSinks
  3. Event Reception: Each isolate subscribes to the broadcast stream via EventChannel
  4. Filtering: The Dart layer filters events by type using the on() method

API Reference #

InterIsolateEventChannel.emit(String eventType, dynamic payload) #

Broadcasts an event to all isolates/engines.

Parameters:

  • eventType (String): Event type identifier (e.g., 'call.invite', 'message.new')
  • payload (dynamic): Data to send (must be JSON-serializable)
    • Supported types: null, bool, num (int, double), String, List, Map
    • List and Map are validated recursively
    • Map keys must be String

Returns: Future<void>

Throws:

  • ArgumentError: If eventType is empty or payload is not JSON-serializable
  • PlatformException: If the native platform encounters an error

Examples:

// Valid payloads
await InterIsolateEventChannel.emit('event', null);
await InterIsolateEventChannel.emit('event', 'text');
await InterIsolateEventChannel.emit('event', 42);
await InterIsolateEventChannel.emit('event', [1, 2, 3]);
await InterIsolateEventChannel.emit('event', {'key': 'value'});
await InterIsolateEventChannel.emit('event', {
  'nested': {'data': [1, 2, 3]},
  'list': ['a', 'b', 'c'],
});

// Invalid payloads (throws ArgumentError)
await InterIsolateEventChannel.emit('event', DateTime.now()); // ❌
await InterIsolateEventChannel.emit('event', MyCustomClass()); // ❌
await InterIsolateEventChannel.emit('event', {1: 'value'}); // ❌ Non-String key

InterIsolateEventChannel.on<T>(String eventType) #

Returns a stream for a specific event type.

Type Parameters:

  • T: Expected payload type (default: dynamic)
    • Explicitly specifying the type is recommended for type safety
    • Events with mismatched types are automatically skipped

Parameters:

  • eventType (String): Event type to subscribe to

Returns: Stream<T> - Stream containing only the payload (cast to type T)

Throws:

  • ArgumentError: If eventType is empty

Examples:

// With Generic type (recommended)
InterIsolateEventChannel.on<Map<String, dynamic>>('user.login').listen((payload) {
  String userId = payload['userId']; // Type safe
});

// Without Generic
InterIsolateEventChannel.on('user.login').listen((payload) {
  // payload is dynamic
});

Limitations #

  • Only supports communication within the same process (no cross-process support)
  • Event payloads must be JSON-serializable (Map, List, String, int, double, bool, null)
  • No event acknowledgement support
  • Broadcast-only (cannot target specific recipients)

Troubleshooting #

Events Not Being Received #

  1. Ensure on() subscription is set up before calling emit()
  2. Verify event type strings match exactly (case-sensitive)
  3. Confirm payload is JSON-serializable

Preventing Memory Leaks #

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = InterIsolateEventChannel.on('my.event').listen((data) {
      // Handle event
    });
  }

  @override
  void dispose() {
    _subscription?.cancel(); // Always cancel!
    super.dispose();
  }
}

Contributing #

Bug reports, feature requests, and pull requests are welcome!

Visit the GitHub repository to submit issues or contribute.

License #

This project is distributed under the MIT License. See the LICENSE file for details.

Author #

Minseok Joel

Changelog #

See CHANGELOG.md for version history.

1
likes
145
points
176
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for broadcasting events across multiple isolates/engines via native platform channels.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on inter_isolate_event_channel

Packages that implement inter_isolate_event_channel