@@ -2786,6 +2786,7 @@ class Navigator extends StatefulWidget {
2786
2786
// \ |
2787
2787
// dispose*
2788
2788
// |
2789
+ // disposing
2789
2790
// |
2790
2791
// disposed
2791
2792
// |
@@ -2821,6 +2822,9 @@ enum _RouteLifecycle {
2821
2822
removing, // we are waiting for subsequent routes to be done animating, then will switch to dispose
2822
2823
// routes that are completely removed from the navigator and overlay.
2823
2824
dispose, // we will dispose the route momentarily
2825
+ disposing, // The entry is waiting for its widget subtree to be disposed
2826
+ // first. It is stored in _entryWaitingForSubTreeDisposal while
2827
+ // awaiting that.
2824
2828
disposed, // we have disposed the route
2825
2829
}
2826
2830
@@ -3047,9 +3051,26 @@ class _RouteEntry extends RouteTransitionRecord {
3047
3051
currentState = _RouteLifecycle .dispose;
3048
3052
}
3049
3053
3050
- void dispose () {
3054
+ /// Disposes this route entry and its [route] immediately.
3055
+ ///
3056
+ /// This method does not wait for the widget subtree of the [route] to unmount
3057
+ /// before disposing.
3058
+ void forcedDispose () {
3051
3059
assert (currentState.index < _RouteLifecycle .disposed.index);
3052
3060
currentState = _RouteLifecycle .disposed;
3061
+ route.dispose ();
3062
+ }
3063
+
3064
+ /// Disposes this route entry and its [route] .
3065
+ ///
3066
+ /// This method waits for the widget subtree of the [route] to unmount before
3067
+ /// disposing. If subtree is already unmounted, this method calls
3068
+ /// [forcedDispose] immediately.
3069
+ ///
3070
+ /// Use [forcedDispose] if the [route] need to be disposed immediately.
3071
+ void dispose () {
3072
+ assert (currentState.index < _RouteLifecycle .disposing.index);
3073
+ currentState = _RouteLifecycle .disposing;
3053
3074
3054
3075
// If the overlay entries are still mounted, widgets in the route's subtree
3055
3076
// may still reference resources from the route and we delay disposal of
@@ -3060,24 +3081,43 @@ class _RouteEntry extends RouteTransitionRecord {
3060
3081
final Iterable <OverlayEntry > mountedEntries = route.overlayEntries.where ((OverlayEntry e) => e.mounted);
3061
3082
3062
3083
if (mountedEntries.isEmpty) {
3063
- route.dispose ();
3064
- } else {
3065
- int mounted = mountedEntries.length;
3066
- assert (mounted > 0 );
3067
- for (final OverlayEntry entry in mountedEntries) {
3068
- late VoidCallback listener;
3069
- listener = () {
3070
- assert (mounted > 0 );
3071
- assert (! entry.mounted);
3072
- mounted-- ;
3073
- entry.removeListener (listener);
3074
- if (mounted == 0 ) {
3075
- assert (route.overlayEntries.every ((OverlayEntry e) => ! e.mounted));
3076
- route.dispose ();
3077
- }
3078
- };
3079
- entry.addListener (listener);
3080
- }
3084
+ forcedDispose ();
3085
+ return ;
3086
+ }
3087
+
3088
+ int mounted = mountedEntries.length;
3089
+ assert (mounted > 0 );
3090
+ final NavigatorState navigator = route._navigator! ;
3091
+ navigator._entryWaitingForSubTreeDisposal.add (this );
3092
+ for (final OverlayEntry entry in mountedEntries) {
3093
+ late VoidCallback listener;
3094
+ listener = () {
3095
+ assert (mounted > 0 );
3096
+ assert (! entry.mounted);
3097
+ mounted-- ;
3098
+ entry.removeListener (listener);
3099
+ if (mounted == 0 ) {
3100
+ assert (route.overlayEntries.every ((OverlayEntry e) => ! e.mounted));
3101
+ // This is a listener callback of one of the overlayEntries in this
3102
+ // route. Disposing the route also disposes its overlayEntries and
3103
+ // violates the rule that a change notifier can't be disposed during
3104
+ // its notifying callback.
3105
+ //
3106
+ // Use a microtask to ensure the overlayEntries have finished
3107
+ // notifying their listeners before disposing.
3108
+ return scheduleMicrotask (() {
3109
+ if (! navigator._entryWaitingForSubTreeDisposal.remove (this )) {
3110
+ // This route must have been destroyed as a result of navigator
3111
+ // force dispose.
3112
+ assert (route._navigator == null && ! navigator.mounted);
3113
+ return ;
3114
+ }
3115
+ assert (currentState == _RouteLifecycle .disposing);
3116
+ forcedDispose ();
3117
+ });
3118
+ }
3119
+ };
3120
+ entry.addListener (listener);
3081
3121
}
3082
3122
}
3083
3123
@@ -3257,6 +3297,15 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
3257
3297
class NavigatorState extends State <Navigator > with TickerProviderStateMixin , RestorationMixin {
3258
3298
late GlobalKey <OverlayState > _overlayKey;
3259
3299
List <_RouteEntry > _history = < _RouteEntry > [];
3300
+ /// A set for entries that are waiting to dispose until their subtrees are
3301
+ /// disposed.
3302
+ ///
3303
+ /// These entries are not considered to be in the _history and will usually
3304
+ /// remove themselves from this set once they can dispose.
3305
+ ///
3306
+ /// The navigator keep track of these entries so that, in case the navigator
3307
+ /// itself is disposed, it can dispose these entries immediately.
3308
+ final Set <_RouteEntry > _entryWaitingForSubTreeDisposal = < _RouteEntry > {};
3260
3309
final _HistoryProperty _serializableHistory = _HistoryProperty ();
3261
3310
final Queue <_NavigatorObservation > _observedRouteAdditions = Queue <_NavigatorObservation >();
3262
3311
final Queue <_NavigatorObservation > _observedRouteDeletions = Queue <_NavigatorObservation >();
@@ -3338,9 +3387,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
3338
3387
registerForRestoration (_serializableHistory, 'history' );
3339
3388
3340
3389
// Delete everything in the old history and clear the overlay.
3341
- while (_history.isNotEmpty) {
3342
- _history.removeLast ().dispose ();
3343
- }
3390
+ _forcedDisposeAllRouteEntries ();
3344
3391
assert (_history.isEmpty);
3345
3392
_overlayKey = GlobalKey <OverlayState >();
3346
3393
@@ -3423,6 +3470,28 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
3423
3470
}
3424
3471
}
3425
3472
3473
+ /// Dispose all lingering router entries immediately.
3474
+ void _forcedDisposeAllRouteEntries () {
3475
+ _entryWaitingForSubTreeDisposal.removeWhere ((_RouteEntry entry) {
3476
+ entry.forcedDispose ();
3477
+ return true ;
3478
+ });
3479
+ while (_history.isNotEmpty) {
3480
+ _disposeRouteEntry (_history.removeLast (), graceful: false );
3481
+ }
3482
+ }
3483
+
3484
+ static void _disposeRouteEntry (_RouteEntry entry, {required bool graceful}) {
3485
+ for (final OverlayEntry overlayEntry in entry.route.overlayEntries) {
3486
+ overlayEntry.remove ();
3487
+ }
3488
+ if (graceful) {
3489
+ entry.dispose ();
3490
+ } else {
3491
+ entry.forcedDispose ();
3492
+ }
3493
+ }
3494
+
3426
3495
void _updateHeroController (HeroController ? newHeroController) {
3427
3496
if (_heroControllerFromScope != newHeroController) {
3428
3497
if (newHeroController != null ) {
@@ -3595,9 +3664,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
3595
3664
}());
3596
3665
_updateHeroController (null );
3597
3666
focusNode.dispose ();
3598
- for (final _RouteEntry entry in _history) {
3599
- entry.dispose ();
3600
- }
3667
+ _forcedDisposeAllRouteEntries ();
3601
3668
_rawNextPagelessRestorationScopeId.dispose ();
3602
3669
_serializableHistory.dispose ();
3603
3670
userGestureInProgressNotifier.dispose ();
@@ -4022,6 +4089,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
4022
4089
// Delay disposal until didChangeNext/didChangePrevious have been sent.
4023
4090
toBeDisposed.add (_history.removeAt (index));
4024
4091
entry = next;
4092
+ case _RouteLifecycle .disposing:
4025
4093
case _RouteLifecycle .disposed:
4026
4094
case _RouteLifecycle .staging:
4027
4095
assert (false );
@@ -4051,10 +4119,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
4051
4119
// Lastly, removes the overlay entries of all marked entries and disposes
4052
4120
// them.
4053
4121
for (final _RouteEntry entry in toBeDisposed) {
4054
- for (final OverlayEntry overlayEntry in entry.route.overlayEntries) {
4055
- overlayEntry.remove ();
4056
- }
4057
- entry.dispose ();
4122
+ _disposeRouteEntry (entry, graceful: true );
4058
4123
}
4059
4124
if (rearrangeOverlay) {
4060
4125
overlay? .rearrange (_allRouteOverlayEntries);
0 commit comments