zenbus 1.0.1
zenbus: ^1.0.1 copied to clipboard
A high-performance event bus for Flutter
ZenBus #
A high-performance, flexible event bus for Flutter applications with multiple implementation strategies. Choose the implementation that best fits your performance requirements.
✨ Features #
- 🚀 Multiple Implementations - Choose between Stream or Alien Signals based on your needs
- ⚡ High Performance - Up to 51x faster than traditional Stream-based implementations
- 🧠 Memory Efficient - Optimized memory usage with minimal overhead
- 🎯 Type Safe - Full Dart type safety with generics
- 🔍 Event Filtering - Built-in support for conditional event handling
- 📊 Benchmarked - Comprehensive performance and memory benchmarks included
- 🎨 Simple API - Clean, intuitive API that's easy to learn and use
📦 Installation #
Add ZenBus to your pubspec.yaml:
dependencies:
zenbus: ^0.0.1
Then run:
flutter pub get
🚀 Quick Start #
Basic Usage #
import 'package:zenbus/zenbus.dart';
// Create a bus (choose your implementation)
final bus = ZenBus<String>.alienSignals(); // Recommended for best performance
// Listen to events
final subscription = bus.listen((event) {
print('Received: $event');
});
// Fire events
bus.fire('Hello, ZenBus!');
// Clean up
subscription.cancel();
With Event Filtering #
final bus = ZenBus<int>.alienSignals();
// Only receive even numbers
final subscription = bus.listen(
(number) => print('Even number: $number'),
where: (number) => number % 2 == 0,
);
bus.fire(1); // Ignored
bus.fire(2); // Prints: "Even number: 2"
bus.fire(3); // Ignored
bus.fire(4); // Prints: "Even number: 4"
subscription.cancel();
🎯 Choosing an Implementation #
ZenBus provides two implementations, each optimized for different scenarios:
1. Alien Signals (Recommended) 🏆 #
final bus = ZenBus<T>.alienSignals();
Best for:
- ✅ High-performance applications
- ✅ Many listeners per bus (10+)
- ✅ High event throughput
- ✅ Production applications
- ✅ Memory-constrained environments
Performance:
- 🚀 5.5M ops/s - Event firing (no listeners)
- 🚀 8.6M ops/s - Event firing (100 listeners)
- 🧠 3.4KB/listener - Memory usage
- 📈 Excellent scaling - Performance stays consistent with many listeners
2. Stream #
final bus = ZenBus<T>.stream();
Best for:
- ✅ Standard Dart async patterns
- ✅ Very few listeners (< 5)
- ✅ Frequent subscription changes
- ✅ Familiarity with Dart Streams
Performance:
- 🐌 1.9M ops/s - Event firing (no listeners)
- ⚠️ 169K ops/s - Event firing (100 listeners) - 51x slower!
- 🧠 3.1KB/listener - Memory usage
- ⚠️ Poor scaling - Performance degrades significantly with many listeners
📚 Usage Examples #
Example 1: Simple Event Bus #
import 'package:zenbus/zenbus.dart';
void main() {
// Create a bus for string messages
final messageBus = ZenBus<String>.alienSignals();
// Subscribe to messages
final subscription = messageBus.listen((message) {
print('📨 $message');
});
// Send messages
messageBus.fire('Hello!');
messageBus.fire('How are you?');
// Clean up
subscription.cancel();
}
Example 2: Multiple Listeners #
final bus = ZenBus<String>.alienSignals();
// Add multiple listeners
final sub1 = bus.listen((msg) => print('Listener 1: $msg'));
final sub2 = bus.listen((msg) => print('Listener 2: $msg'));
final sub3 = bus.listen((msg) => print('Listener 3: $msg'));
// All listeners receive the event
bus.fire('Broadcast message');
// Output:
// Listener 1: Broadcast message
// Listener 2: Broadcast message
// Listener 3: Broadcast message
// Cancel all subscriptions
sub1.cancel();
sub2.cancel();
sub3.cancel();
Example 3: Typed Events #
// Define your event types
class UserLoggedIn {
final String username;
UserLoggedIn(this.username);
}
class UserLoggedOut {
final String username;
UserLoggedOut(this.username);
}
// Create typed buses
final loginBus = ZenBus<UserLoggedIn>.alienSignals();
final logoutBus = ZenBus<UserLoggedOut>.alienSignals();
// Subscribe to login events
loginBus.listen((event) {
print('👤 ${event.username} logged in');
});
// Subscribe to logout events
logoutBus.listen((event) {
print('👋 ${event.username} logged out');
});
// Fire events
loginBus.fire(UserLoggedIn('alice'));
logoutBus.fire(UserLoggedOut('bob'));
Example 4: Event Filtering #
enum Priority { low, medium, high, critical }
class Task {
final String name;
final Priority priority;
Task(this.name, this.priority);
}
final taskBus = ZenBus<Task>.alienSignals();
// Only handle critical tasks
final criticalSub = taskBus.listen(
(task) => print('🚨 CRITICAL: ${task.name}'),
where: (task) => task.priority == Priority.critical,
);
// Only handle high priority or above
final highPrioritySub = taskBus.listen(
(task) => print('⚠️ HIGH: ${task.name}'),
where: (task) => task.priority == Priority.high ||
task.priority == Priority.critical,
);
// Fire various tasks
taskBus.fire(Task('Update docs', Priority.low)); // No output
taskBus.fire(Task('Fix bug', Priority.medium)); // No output
taskBus.fire(Task('Security patch', Priority.high)); // ⚠️ HIGH: Security patch
taskBus.fire(Task('Server down', Priority.critical)); // 🚨 CRITICAL: Server down
// ⚠️ HIGH: Server down
Example 5: Flutter Integration #
import 'package:flutter/material.dart';
import 'package:zenbus/zenbus.dart';
// Global event bus
final appEventBus = ZenBus<AppEvent>.alienSignals();
abstract class AppEvent {}
class ThemeChanged extends AppEvent {
final bool isDark;
ThemeChanged(this.isDark);
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late ZenBusSubscription<AppEvent> _subscription;
bool _isDarkMode = false;
@override
void initState() {
super.initState();
// Listen to theme changes
_subscription = appEventBus.listen((event) {
event = event as ThemeChanged;
setState(() => _isDarkMode = event.isDark);
},
where: (event) => event is ThemeChanged,
);
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: HomePage(),
);
}
}
// Somewhere in your app
ElevatedButton(
onPressed: () {
appEventBus.fire(ThemeChanged(true));
},
child: Text('Enable Dark Mode'),
);
📊 Performance Benchmarks #
Comprehensive benchmarks comparing both implementations:
Event Firing Performance #
| Scenario | Stream | Alien Signals | Winner |
|---|---|---|---|
| No listeners | 1.9M ops/s | 5.5M ops/s | Alien Signals (2.9x) |
| 1 listener | 1.9M ops/s | 6.9M ops/s | Alien Signals (3.6x) |
| 10 listeners | 1.3M ops/s | 7.4M ops/s | Alien Signals (5.8x) |
| 100 listeners | 169K ops/s ⚠️ | 8.6M ops/s | Alien Signals (51x) |
| With filter | 2.5M ops/s | 9.0M ops/s | Alien Signals (3.5x) |
Memory Usage #
| Scenario | Stream | Alien Signals | Winner |
|---|---|---|---|
| Bus creation (1000 instances) | 18.2KB/bus | -562B/bus | Alien Signals |
| Listener registration | 3.1KB/listener | 3.4KB/listener | Stream |
| Filtered listeners | -1.1KB/listener | 3.4KB/listener | Alien Signals |
Run Benchmarks Yourself #
cd packages/zenbus
# Performance benchmarks
dart run benchmark/zenbus_benchmark.dart
# Memory benchmarks (requires --observe flag)
./benchmark/run_memory_benchmarks.sh
See detailed reports:
🎨 API Reference #
ZenBus #
The main event bus class.
Constructors
// Create a Stream-based bus
ZenBus<T>.stream()
// Create an Alien Signals-based bus (recommended)
ZenBus<T>.alienSignals()
Methods
// Fire an event to all listeners
void fire(T event)
// Subscribe to events
ZenBusSubscription<T> listen(
void Function(T event) listener, {
bool Function(T event)? where,
})
ZenBusSubscription #
Represents an active subscription to a bus.
Methods
// Cancel the subscription
void cancel()
🤔 FAQ #
When should I use ZenBus over other event bus solutions? #
Use ZenBus when:
- You need high performance event handling
- You have many listeners per event type
- You want flexibility to choose implementation strategies
- Memory efficiency is important
- You need type-safe event handling
Which implementation should I choose? #
For most applications: Use ZenBus.alienSignals() - it offers the best overall performance and memory efficiency.
Use Stream if:
- You have very few listeners (< 5)
- You frequently add/remove subscriptions
- You prefer standard Dart patterns
Can I use multiple implementations in the same app? #
Yes! You can mix and match implementations based on your needs:
// High-traffic bus with many listeners
final messageBus = ZenBus<Message>.alienSignals();
// Low-traffic bus with few listeners
final settingsBus = ZenBus<Settings>.stream();
How do I handle errors in listeners? #
Wrap your listener code in try-catch:
bus.listen((event) {
try {
// Your event handling code
processEvent(event);
} catch (e) {
print('Error handling event: $e');
}
});
Is ZenBus thread-safe? #
ZenBus follows Dart's single-threaded model. All events are fired synchronously on the same isolate. For multi-isolate communication, use Dart's SendPort/ReceivePort mechanism.
🛠️ Best Practices #
1. Always Cancel Subscriptions #
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ZenBusSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = bus.listen((event) { /* ... */ });
}
@override
void dispose() {
_subscription.cancel(); // ✅ Always cancel!
super.dispose();
}
@override
Widget build(BuildContext context) => Container();
}
2. Use Type-Safe Events #
// ❌ Bad: Using dynamic or Object
final bus = ZenBus<dynamic>.alienSignals();
// ✅ Good: Use specific types
final bus = ZenBus<UserEvent>.alienSignals();
3. Keep Listeners Lightweight #
// ❌ Bad: Heavy processing in listener
bus.listen((event) {
performExpensiveOperation();
updateDatabase();
callApi();
});
// ✅ Good: Offload heavy work
bus.listen((event) async {
// Quick synchronous work
updateUI();
// Heavy work in background
await Future.microtask(() {
performExpensiveOperation();
});
});
4. Use Filtering for Conditional Logic #
// ❌ Bad: Filtering in listener
bus.listen((event) {
if (event.priority == Priority.high) {
handleHighPriority(event);
}
});
// ✅ Good: Use where parameter
bus.listen(
(event) => handleHighPriority(event),
where: (event) => event.priority == Priority.high,
);
5. Consider Using a Service Locator #
// services/event_bus_service.dart
class EventBusService {
static final messages = ZenBus<String>.alienSignals();
static final users = ZenBus<UserEvent>.alienSignals();
static final navigation = ZenBus<NavigationEvent>.alienSignals();
}
// Usage
EventBusService.messages.fire('Hello!');
🤝 Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License #
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments #
- Built with alien_signals
- Inspired by various event bus implementations in the Flutter ecosystem
📞 Support #
Made with ❤️ by Bui Dai Duong