101 lines
3.4 KiB
Dart
101 lines
3.4 KiB
Dart
import 'package:votianlt_app/services/developer.dart' as developer;
|
|
|
|
/// A lightweight in-app message bus ("dart_mq") for pub/sub style communication.
|
|
///
|
|
// Usage:
|
|
// final mq = DartMQ();
|
|
// final sub = mq.subscribe<bool>('connection/status', (isOnline) { /* ... */ });
|
|
// mq.publish('connection/status', true);
|
|
// sub.cancel();
|
|
class DartMQ {
|
|
DartMQ._internal();
|
|
static final DartMQ _instance = DartMQ._internal();
|
|
factory DartMQ() => _instance;
|
|
|
|
final Map<String, List<_DartMQSubscriber>> _subscribers = {};
|
|
|
|
/// Subscribe to a topic. Returns a cancellable subscription handle.
|
|
DartMQSubscription subscribe<T>(String topic, void Function(T data) handler) {
|
|
final sub = _DartMQSubscriber<T>(topic: topic, handler: handler);
|
|
final list = _subscribers.putIfAbsent(topic, () => <_DartMQSubscriber>[]);
|
|
list.add(sub);
|
|
return DartMQSubscription._(this, sub);
|
|
}
|
|
|
|
/// Publish a message to a topic. If no subscribers exist, this is a no-op.
|
|
void publish<T>(String topic, T data) {
|
|
final list = _subscribers[topic];
|
|
if (list == null || list.isEmpty) return;
|
|
|
|
// Make a defensive copy to allow cancellation during iteration.
|
|
final current = List<_DartMQSubscriber>.from(list);
|
|
for (final s in current) {
|
|
// Only deliver if types match; otherwise, try dynamic fallback
|
|
if (s is _DartMQSubscriber<T>) {
|
|
try {
|
|
s.handler(data);
|
|
} catch (e, stackTrace) {
|
|
developer.log(
|
|
'Error delivering message to subscriber on topic "$topic": $e',
|
|
);
|
|
developer.log('Stack trace: $stackTrace');
|
|
}
|
|
} else {
|
|
// Fallback delivery for handlers expecting dynamic or different T
|
|
try {
|
|
final dynamicHandler = s.handler as dynamic;
|
|
dynamicHandler(data);
|
|
} catch (e, stackTrace) {
|
|
developer.log(
|
|
'Error delivering dynamic message to subscriber on topic "$topic": $e',
|
|
);
|
|
developer.log('Stack trace: $stackTrace');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _cancel(_DartMQSubscriber subscriber) {
|
|
final list = _subscribers[subscriber.topic];
|
|
if (list == null) return;
|
|
list.remove(subscriber);
|
|
if (list.isEmpty) {
|
|
_subscribers.remove(subscriber.topic);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cancellable subscription handle
|
|
class DartMQSubscription {
|
|
final DartMQ _mq;
|
|
final _DartMQSubscriber _subscriber;
|
|
bool _isCancelled = false;
|
|
|
|
DartMQSubscription._(this._mq, this._subscriber);
|
|
|
|
void cancel() {
|
|
if (_isCancelled) return;
|
|
_isCancelled = true;
|
|
_mq._cancel(_subscriber);
|
|
}
|
|
}
|
|
|
|
class _DartMQSubscriber<T> {
|
|
final String topic;
|
|
final void Function(T data) handler;
|
|
_DartMQSubscriber({required this.topic, required this.handler});
|
|
}
|
|
|
|
/// Common topics used in the app
|
|
class MQTopics {
|
|
static const connectionStatus = 'connection/status'; // bool
|
|
static const authResponse = 'auth/response'; // Map<String, dynamic>
|
|
static const jobsResponse = 'jobs/response'; // List<dynamic>
|
|
static const taskEvents = 'task/events'; // Map<String, dynamic>
|
|
static const jobsUpdated = 'app/jobsUpdated'; // void/null
|
|
static const jobDeleted = 'job/deleted'; // Map<String, dynamic> {jobId, jobNumber, deletedAt}
|
|
static const jobCreated = 'job/created'; // Map<String, dynamic> - full job data
|
|
static const chatIncoming = 'chat/incoming'; // ChatMessage
|
|
static const chatOutgoing = 'chat/outgoing'; // ChatMessage
|
|
}
|