Skip to content

Commit 7bed3c6

Browse files
fix: rename dispose to free to avoid collisions
Co-authored-by: MasterMarcoHD <MasterMarcoHD@users.noreply.github.com>
1 parent e4f2d9d commit 7bed3c6

16 files changed

+263
-118
lines changed

example/routing.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ Future<void> main() async {
6868

6969
final router = GetIt.I<RoutingService<String, _Config>>();
7070

71-
await router.navigate('/test/123we/a', callback: print);
71+
await router.navigate(
72+
'/test/123we/a',
73+
callback: (view, preview) {
74+
print('Navigation callback: preview=$preview, view=$view');
75+
},
76+
);
7277

7378
print(router.currentContext);
7479

lib/src/domain/services/routing_service.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ abstract class RoutingService<T, Config extends Object> extends Service {
1515
Future<void> navigate(
1616
String path, {
1717
bool skipPreview = false,
18-
required void Function(T) callback,
18+
required void Function(T, bool) callback,
1919
});
2020

2121
/// Checks if the specified [path] is currently active.

lib/src/infra/services/noop_telemetry_service.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:grumpy/grumpy.dart';
24

35
/// A no-operation implementation of [TelemetryService].
@@ -6,4 +8,17 @@ class NoopTelemetryService extends TelemetryService {
68
noSuchMethod(Invocation invocation) {
79
log('NoopTelemetryService: ${invocation.memberName} called.');
810
}
11+
12+
@override
13+
Future<T> runSpan<T>(
14+
String name,
15+
FutureOr<T> Function() callback, {
16+
Map<String, dynamic>? attributes,
17+
}) async {
18+
log(
19+
'NoopTelemetryService: runSpan $name called with attributes: $attributes',
20+
);
21+
22+
return callback();
23+
}
924
}

lib/src/infra/services/routingkit_routing_service.dart

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class RoutingKitRoutingService<T, Config extends Object>
99
with LifecycleMixin {
1010
RouteContext? _context;
1111

12+
final Map<Uri, (Future<bool>, LeafRoute<T, Config>)> _pendingNavigations = {};
13+
1214
/// The root module of the application.
1315
final RootModule<T, Config> rootModule;
1416

@@ -30,8 +32,8 @@ class RoutingKitRoutingService<T, Config extends Object>
3032
RouteContext? get currentContext => _context;
3133

3234
@override
33-
FutureOr<void> dispose() {
34-
super.dispose();
35+
FutureOr<void> free() {
36+
super.free();
3537
_listeners.clear();
3638
}
3739

@@ -116,82 +118,145 @@ class RoutingKitRoutingService<T, Config extends Object>
116118
Future<void> navigate(
117119
String path, {
118120
bool skipPreview = false,
119-
required void Function(T) callback,
121+
required void Function(T, bool) callback,
120122
}) async {
121123
final uri = Uri.parse(path);
122124

123125
if (uri == currentContext?.uri) {
124126
log('Already at path: $path, skipping navigation.');
127+
125128
return;
126129
}
127130

128-
final cleanPath = uri.path;
131+
if (_pendingNavigations.containsKey(uri)) {
132+
log('Navigation to $path is already in progress, forwarding callback.');
129133

130-
// find the route
131-
final match = _kit.find(null, cleanPath);
134+
final (future, leaf) = _pendingNavigations[uri]!;
132135

133-
if (match == null) {
134-
throw ArgumentError.value(
135-
path,
136-
'path',
137-
'No route found for the given path!',
138-
);
139-
}
136+
if (!skipPreview) {
137+
callback(leaf.view.preview(RouteContext.fromUri(uri)), true);
138+
}
140139

141-
var leaf = match.data;
140+
log('Waiting for pending navigation to $path to complete.');
142141

143-
if (leaf is ModuleRoute<T, Config>) {
144-
leaf =
145-
leaf.root ??
146-
(throw ArgumentError.value(
147-
path,
148-
'path',
149-
'Resolved ModuleRoute does not have a root LeafRoute defined!',
150-
));
151-
}
142+
final success = await future;
143+
144+
if (!success) {
145+
log(
146+
'Pending navigation to $path failed, not invoking content callback.',
147+
);
148+
return;
149+
}
152150

153-
// check if leaf (throw if not)
154-
if (leaf is! LeafRoute) {
155-
throw ArgumentError.value(path, 'path', 'Resolved route is not a leaf!');
151+
log('Pending navigation to $path completed, invoking content callback.');
152+
153+
callback(await leaf.view.content(RouteContext.fromUri(uri)), false);
154+
155+
return;
156156
}
157157

158-
leaf as LeafRoute<T, Config>;
158+
try {
159+
final cleanPath = uri.path;
160+
161+
// find the route
162+
final match = _kit.find(null, cleanPath);
163+
164+
if (match == null) {
165+
throw ArgumentError.value(
166+
path,
167+
'path',
168+
'No route found for the given path!',
169+
);
170+
}
171+
172+
var leaf = match.data;
159173

160-
// find context
174+
if (leaf is ModuleRoute<T, Config>) {
175+
leaf =
176+
leaf.root ??
177+
(throw ArgumentError.value(
178+
path,
179+
'path',
180+
'Resolved ModuleRoute does not have a root LeafRoute defined!',
181+
));
182+
}
183+
184+
// check if leaf (throw if not)
185+
if (leaf is! LeafRoute) {
186+
throw ArgumentError.value(
187+
path,
188+
'path',
189+
'Resolved route is not a leaf!',
190+
);
191+
}
192+
193+
leaf as LeafRoute<T, Config>;
194+
195+
final future = _navigate(uri, leaf, skipPreview, callback);
196+
197+
_pendingNavigations[uri] = (future, leaf);
198+
await future;
199+
} catch (e, s) {
200+
log('Navigation to $path failed with error', e, s);
201+
rethrow;
202+
} finally {
203+
_pendingNavigations.remove(uri);
204+
}
205+
}
206+
207+
Future<bool> _navigate(
208+
Uri uri,
209+
LeafRoute<T, Config> leaf,
210+
bool skipPreview,
211+
void Function(T, bool) callback,
212+
) async {
161213
var context = RouteContext.fromUri(uri);
214+
final cleanPath = uri.path;
162215

163-
log('Navigating to $path with context: $context');
216+
log('Navigating to $cleanPath with context: $context');
164217

165-
if (!skipPreview) callback(leaf.view.preview(context));
218+
if (!skipPreview) callback(leaf.view.preview(context), true);
166219

167220
// activate required modules
168221
final dependencies = getDependencies(cleanPath);
169222

170223
for (Module<T, Config> module in dependencies) {
171-
module.activate();
224+
await module.activate();
172225
log('Activated module: ${module.runtimeType}');
173226
}
174227

175228
// run middlewares (if any)
176229
try {
177-
for (final middleware in leaf.middleware) {
230+
for (var i = 0; i < leaf.middleware.length; i++) {
231+
final middleware = leaf.middleware[i];
232+
log(
233+
'Executing middleware ${i + 1}/${leaf.middleware.length}: ${middleware.runtimeType}',
234+
);
178235
context = await middleware(context);
179-
log('Middleware processed context: $context');
180236
}
237+
log(
238+
'All ${leaf.middleware.length} middlewares executed successfully for $cleanPath',
239+
);
181240
} catch (e, s) {
182-
log('A middleware threw an exception during navigation to $path', e, s);
183-
rethrow;
241+
log(
242+
'A middleware threw an exception during navigation to $cleanPath',
243+
e,
244+
s,
245+
);
246+
return false;
184247
}
185248

186249
_context = context;
187250

188-
callback(await leaf.view.content(context));
251+
callback(await leaf.view.content(context), false);
189252

190-
log('Activated route at $path');
253+
log('Activated route at $cleanPath');
191254

192255
// notify listeners
193256
for (final listener in _listeners) {
194257
listener(leaf);
195258
}
259+
260+
return true;
196261
}
197262
}

lib/src/module.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ abstract class Module<RouteType, Config extends Object>
101101
await _mount(module);
102102
}
103103

104-
_di.pushNewScope(scopeName: runtimeType.toString(), dispose: dispose);
104+
_di.pushNewScope(scopeName: runtimeType.toString(), dispose: free);
105105

106106
bindExternalDeps(<T extends Object>(builder) {
107107
_di.registerSingleton<T>(builder(_di.get<Config>(), _di.get));
@@ -128,19 +128,19 @@ abstract class Module<RouteType, Config extends Object>
128128
},
129129
dispose: (repo) async {
130130
await repo.deactivate();
131-
await repo.dispose();
131+
await repo.free();
132132
},
133133
);
134134
});
135135
}
136136

137137
@override
138138
@mustCallSuper
139-
FutureOr<void> dispose() async {
139+
FutureOr<void> free() async {
140140
if (_disposed) return;
141141
_disposed = true;
142142

143-
await super.dispose();
143+
await super.free();
144144

145145
if (_firstImportScope != null) {
146146
await _di.popScopesTill(_firstImportScope!);
@@ -211,7 +211,7 @@ abstract class RootModule<RouteType, Config extends Object>
211211
@override
212212
// if the root module is disposed, something is very wrong.
213213
// ignore: must_call_super
214-
FutureOr<void> dispose() {
214+
FutureOr<void> free() {
215215
throw StateError(
216216
'RootModule should not be disposed. It lives throughout the application lifecycle.',
217217
);

lib/src/utils/disposable.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import 'package:meta/meta.dart';
55
/// An interface for disposable resources.
66
mixin Disposable on Object implements get_it.Disposable {
77
/// Disposes of the resource.
8-
FutureOr<void> dispose();
8+
FutureOr<void> free();
99

1010
@override
1111
@nonVirtual
12-
FutureOr<dynamic> onDispose() => dispose();
12+
FutureOr<dynamic> onDispose() => free();
1313
}

lib/src/utils/lifecycle_hooks_mixin.dart

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,46 @@ mixin LifecycleHooksMixin on LifecycleMixin, LogMixin {
1212
final List<FutureOr<void> Function()> _disposeHooks = [];
1313

1414
/// Registers a hook to be called in [initialize].
15+
///
16+
/// Note: It is unsafe to access the same variables in multiple hooks
17+
/// registered via this method, as the order of execution is not guaranteed.
18+
/// Use a single hook to manage shared state.
1519
void onInitialize(FutureOr<void> Function() hook) {
1620
_initializeHooks.add(hook);
1721
}
1822

1923
/// Registers a hook to be called in [activate].
24+
///
25+
/// Note: It is unsafe to access the same variables in multiple hooks
26+
/// registered via this method, as the order of execution is not guaranteed.
27+
/// Use a single hook to manage shared state.
2028
void onActivate(FutureOr<void> Function() hook) {
2129
_activateHooks.add(hook);
2230
}
2331

2432
/// Registers a hook to be called in [deactivate].
33+
///
34+
/// Note: It is unsafe to access the same variables in multiple hooks
35+
/// registered via this method, as the order of execution is not guaranteed.
36+
/// Use a single hook to manage shared state.
2537
void onDeactivate(FutureOr<void> Function() hook) {
2638
_deactivateHooks.add(hook);
2739
}
2840

2941
/// Registers a hook to be called in [dependenciesChanged].
42+
///
43+
/// Note: It is unsafe to access the same variables in multiple hooks
44+
/// registered via this method, as the order of execution is not guaranteed.
45+
/// Use a single hook to manage shared state.
3046
void onDependenciesChanged(FutureOr<void> Function() hook) {
3147
_dependenciesChangedHooks.add(hook);
3248
}
3349

34-
/// Registers a hook to be called in [dispose].
50+
/// Registers a hook to be called in [free].
51+
///
52+
/// Note: It is unsafe to access the same variables in multiple hooks
53+
/// registered via this method, as the order of execution is not guaranteed.
54+
/// Use a single hook to manage shared state.
3555
void onDisposed(FutureOr<void> Function() hook) {
3656
_disposeHooks.add(hook);
3757
}
@@ -75,8 +95,8 @@ mixin LifecycleHooksMixin on LifecycleMixin, LogMixin {
7595

7696
@override
7797
@mustCallSuper
78-
FutureOr<void> dispose() async {
79-
await super.dispose();
98+
FutureOr<void> free() async {
99+
await super.free();
80100

81101
_initializeHooks.clear();
82102
_activateHooks.clear();

lib/src/utils/lifecycle_mixin.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'package:grumpy/grumpy.dart';
1212
/// returning from the background).
1313
/// - [deactivate]: Called when the object is deactivated.
1414
/// - [dependenciesChanged]: Called when the object's dependencies have changed.
15-
/// - [dispose]: Called when the object is being disposed of.
15+
/// - [free]: Called when the object is being disposed of.
1616
abstract mixin class LifecycleMixin implements Disposable {
1717
bool _isDisposed = false;
1818

@@ -43,16 +43,20 @@ abstract mixin class LifecycleMixin implements Disposable {
4343
/// Disposes of the object and releases any resources.
4444
/// This method should be overridden to perform cleanup tasks.
4545
///
46-
/// You can safely assume that [dispose] will be called only once.
46+
/// You can safely assume that [free] will be called only once.
4747
@override
4848
@mustCallSuper
49-
FutureOr<void> dispose() async {
49+
FutureOr<void> free() async {
5050
if (_isDisposed) {
5151
throw StateError('Resource has already been disposed.');
5252
}
5353

5454
_isDisposed = true;
5555
}
56+
57+
@override
58+
@nonVirtual
59+
FutureOr<void> onDispose() => free();
5660
}
5761

5862
/// Mixin that adds lifecycle callbacks to a [Repo].

0 commit comments

Comments
 (0)