Skip to content

Commit 206b27a

Browse files
committed
v6.28.0
1 parent 3c04010 commit 206b27a

File tree

6 files changed

+187
-25
lines changed

6 files changed

+187
-25
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [6.28.0] - 2025-04-24
2+
3+
* Added: `nylo.broadcastEvents()`to broadcast events to all listeners.
4+
* Added: `listenOn` to listen to events in your app. E.g. `listenOn<MyEvent>((event) { });`
5+
* Added: `listen` helper in NyPage and NyState to listen to events in your app. E.g. `listen<MyEvent>((event) { });`
6+
* Updated: `event` helper to support new `broadcast` parameter. E.g. `event<MyEvent>(data: {...}, broadcast: true);`
7+
18
## [6.27.0] - 2025-04-19
29

310
* Small breaking change to `stateActions`:

lib/events/events.dart

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// Base interface for Events
22
abstract class NyEvent {
3-
final Map<Type, NyListener> listeners = {};
3+
final Map listeners = {};
44
}
55

66
/// Base class for listeners
@@ -17,7 +17,134 @@ class NyListener {
1717

1818
/// Handle the payload from the event
1919
/// The [event] argument provides a Map of the data
20-
/// event<ChatCreated>(data: {"chat": Chat()});
21-
/// E.g. [event] = {"chat":"Chat instance"}
22-
Future<dynamic> handle(Map? event) async {}
20+
Future handle(Map? event) async {}
21+
}
22+
23+
class NyEventBus {
24+
static final NyEventBus _instance = NyEventBus._internal();
25+
factory NyEventBus() => _instance;
26+
NyEventBus._internal();
27+
28+
final Map<Type, List<NyListener>> _subscriptions = {};
29+
30+
void on<T extends NyEvent>(NyListener listener) {
31+
_subscriptions[T] ??= [];
32+
if (!_subscriptions[T]!.contains(listener)) {
33+
_subscriptions[T]!.add(listener);
34+
}
35+
}
36+
37+
void off<T extends NyEvent>(NyListener listener) {
38+
if (_subscriptions.containsKey(T)) {
39+
_subscriptions[T]!.remove(listener);
40+
if (_subscriptions[T]!.isEmpty) {
41+
_subscriptions.remove(T);
42+
}
43+
}
44+
}
45+
46+
Future<void> broadcast(NyEvent event, [Map? params]) async {
47+
final Type eventType = event.runtimeType;
48+
49+
if (_subscriptions.containsKey(eventType)) {
50+
final listeners = List<NyListener>.from(_subscriptions[eventType]!);
51+
52+
for (var listener in listeners) {
53+
// Skip if listener was already removed (could happen if returning false in a callback)
54+
if (!_subscriptions.containsKey(eventType) ||
55+
!_subscriptions[eventType]!.contains(listener)) {
56+
continue;
57+
}
58+
59+
listener.setEvent(event);
60+
dynamic result = await listener.handle(params);
61+
62+
// Check if we should stop propagation
63+
if (result != null && result == false) {
64+
break;
65+
}
66+
}
67+
}
68+
}
69+
70+
// Add this method to remove a listener without needing to specify the type
71+
void removeListener(NyListener listener) {
72+
for (var type in _subscriptions.keys.toList()) {
73+
if (_subscriptions[type]!.contains(listener)) {
74+
_subscriptions[type]!.remove(listener);
75+
if (_subscriptions[type]!.isEmpty) {
76+
_subscriptions.remove(type);
77+
}
78+
break; // Assuming a listener is only registered once
79+
}
80+
}
81+
}
82+
}
83+
84+
/// Subscription handle that can be used to cancel listening
85+
class NyEventSubscription<T extends NyEvent> {
86+
final NyListener _listener;
87+
bool _active = true;
88+
89+
NyEventSubscription(this._listener);
90+
91+
/// Cancel this subscription
92+
void cancel() {
93+
if (_active) {
94+
NyEventBus().off<T>(_listener);
95+
_active = false;
96+
}
97+
}
98+
99+
bool get isActive => _active;
100+
}
101+
102+
/// Extension on NyEvent that adds the new functionality
103+
extension NyEventExtension on NyEvent {
104+
// Fire method that works with both existing and new listeners
105+
Future<void> fireAll(Map? params, {bool broadcast = true}) async {
106+
// Execute existing listeners using current pattern
107+
for (var listener in listeners.values.toList()) {
108+
if (listener is NyListener) {
109+
listener.setEvent(this);
110+
dynamic result = await listener.handle(params);
111+
if (result != null && result == false) {
112+
return; // Early termination if a listener returns false
113+
}
114+
}
115+
}
116+
117+
if (!broadcast) return;
118+
// Execute dynamically registered listeners
119+
await NyEventBus().broadcast(this, params);
120+
}
121+
}
122+
123+
class NyEventCallbackListener extends NyListener {
124+
final Function(Map? data) callback;
125+
bool _shouldCancel = false;
126+
127+
NyEventCallbackListener(this.callback);
128+
129+
@override
130+
Future handle(Map? event) async {
131+
final result = callback(event);
132+
133+
// If callback returns false, flag this subscription for cancellation
134+
if (result != null && result == false) {
135+
_shouldCancel = true;
136+
// Use the new method that doesn't require type parameter
137+
NyEventBus().removeListener(this);
138+
return false;
139+
}
140+
return result;
141+
}
142+
143+
bool get shouldCancel => _shouldCancel;
144+
}
145+
146+
NyEventSubscription listenOn<E extends NyEvent>(Function(Map? data) callback) {
147+
final listener = NyEventCallbackListener(callback);
148+
NyEventBus().on<E>(listener);
149+
return NyEventSubscription<E>(listener);
23150
}

lib/helpers/helper.dart

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -110,34 +110,33 @@ String trans(String key, {Map<String, String>? arguments}) =>
110110
NyLocalization.instance.translate(key, arguments);
111111

112112
/// Event helper
113-
nyEvent<T>({
114-
Map? params,
115-
Map<Type, NyEvent> events = const {},
116-
}) async {
113+
nyEvent<T>(
114+
{Map? params,
115+
Map<Type, NyEvent> events = const {},
116+
bool? broadcast}) async {
117117
assert(T.toString() != 'dynamic',
118118
'You must provide an Event type for this method.\nE.g. event<LoginEvent>({"User": "#1 User"});');
119119

120120
Map<Type, NyEvent> appEvents = events;
121121

122-
if (events.isEmpty && Backpack.instance.read('nylo') != null) {
123-
appEvents = Backpack.instance.read('nylo').getEvents();
122+
Nylo? nylo;
123+
if (Backpack.instance.read('nylo') != null) {
124+
nylo = Backpack.instance.read('nylo');
124125
}
126+
127+
if (events.isEmpty && nylo != null) {
128+
appEvents = nylo.getEvents();
129+
}
130+
131+
broadcast ??= nylo?.shouldBroadcastEvents();
132+
125133
assert(appEvents.containsKey(T),
126134
'Your config/events.dart is missing this class ${T.toString()}');
127135

128136
NyEvent nyEvent = appEvents[T]!;
129-
Map<dynamic, NyListener> listeners = nyEvent.listeners;
130137

131-
if (listeners.isEmpty) {
132-
return;
133-
}
134-
for (NyListener listener in listeners.values.toList()) {
135-
listener.setEvent(nyEvent);
136-
dynamic result = await listener.handle(params);
137-
if (result != null && result == false) {
138-
break;
139-
}
140-
}
138+
// Use the extension method
139+
await nyEvent.fireAll(params, broadcast: broadcast ?? false);
141140
}
142141

143142
/// API helper
@@ -244,7 +243,7 @@ Future<dynamic> nyApi<T>(
244243
if (nyEvent == null) {
245244
continue;
246245
}
247-
Map<dynamic, NyListener> listeners = nyEvent.listeners;
246+
Map listeners = nyEvent.listeners;
248247

249248
if (listeners.isEmpty) {
250249
return;
@@ -450,8 +449,8 @@ api<T extends NyApiService>(dynamic Function(T request) request,
450449
/// });
451450
/// ```
452451
/// The above example will send an event to LoginEvent.
453-
event<T>({Map? data}) async =>
454-
await nyEvent<T>(params: data, events: Nylo.events());
452+
event<T>({Map? data, bool? broadcast}) async =>
453+
await nyEvent<T>(params: data, events: Nylo.events(), broadcast: broadcast);
455454

456455
/// Dump a message to the console.
457456
/// Example:

lib/nylo.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Nylo {
7171
_onDidReceiveBackgroundNotificationResponse;
7272
NyCache? _cache;
7373
bool isFlutterLocalNotificationsInitialized = false;
74+
bool? _broadcastEvents;
7475

7576
/// Get the cache instance
7677
NyCache? get getCache => _cache;
@@ -292,6 +293,14 @@ class Nylo {
292293
/// Check if the app should show date time in logs
293294
bool shouldShowDateTimeInLogs() => _showDateTimeInLogs ?? false;
294295

296+
/// Set if you want to broadcast all events
297+
broadcastEvents([bool broadcast = true]) {
298+
_broadcastEvents = broadcast;
299+
}
300+
301+
/// Check if the app should broadcast events
302+
bool shouldBroadcastEvents() => _broadcastEvents ?? false;
303+
295304
/// Add toast notification
296305
addToastNotification(
297306
Widget Function({

lib/widgets/ny_base_state.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:io';
33

44
import 'package:flutter/cupertino.dart';
55
import 'package:flutter/foundation.dart' show kIsWeb;
6+
import '/events/events.dart';
67
import '/helpers/ny_color.dart';
78
import '/helpers/loading_style.dart';
89
import '/helpers/extensions.dart';
@@ -70,6 +71,19 @@ abstract class NyBaseState<T extends StatefulWidget> extends State<T> {
7071
/// Override the loading state.
7172
bool overrideLoading = false;
7273

74+
/// Keep track of event subscriptions
75+
final List<NyEventSubscription> _eventSubscriptions = [];
76+
77+
/// Listen to an event with a callback function
78+
/// Returns a subscription reference that can be used to cancel later
79+
NyEventSubscription listen<E extends NyEvent>(Function(Map? data) callback) {
80+
final listener = NyEventCallbackListener(callback);
81+
NyEventBus().on<E>(listener);
82+
final subscription = NyEventSubscription<E>(listener);
83+
_eventSubscriptions.add(subscription);
84+
return subscription;
85+
}
86+
7387
/// Initialize your widget in [init].
7488
///
7589
/// * [init] is called in the [initState] method.
@@ -264,6 +278,12 @@ abstract class NyBaseState<T extends StatefulWidget> extends State<T> {
264278
eventSubscription?.cancel();
265279
_lockMap = {};
266280
_loadingMap = {};
281+
282+
for (var subscription in _eventSubscriptions) {
283+
subscription.cancel();
284+
}
285+
_eventSubscriptions.clear();
286+
267287
super.dispose();
268288
}
269289

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: nylo_support
22
description: Support library for the Nylo framework. This library supports routing, widgets, localization, cli, storage and more.
3-
version: 6.27.0
3+
version: 6.28.0
44
homepage: https://nylo.dev
55
repository: https://github.com/nylo-core/support/tree/6.x
66
issue_tracker: https://github.com/nylo-core/support/issues

0 commit comments

Comments
 (0)