Files
votianlt/app/lib/services/dart_mq.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
}