Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
ea5cfe09
Unverified
Commit
ea5cfe09
authored
May 19, 2023
by
chunhtai
Committed by
GitHub
May 19, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Properly cleans up routes (#126453)
fixes
https://github.com/flutter/flutter/issues/126100
parent
af83c767
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
143 additions
and
66 deletions
+143
-66
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+37
-30
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+94
-29
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+6
-1
about_test.dart
packages/flutter/test/material/about_test.dart
+4
-4
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+1
-1
routes_test.dart
packages/flutter/test/widgets/routes_test.dart
+1
-1
No files found.
packages/flutter/lib/src/widgets/heroes.dart
View file @
ea5cfe09
...
@@ -849,7 +849,12 @@ class HeroController extends NavigatorObserver {
...
@@ -849,7 +849,12 @@ class HeroController extends NavigatorObserver {
HeroFlightDirection
flightType
,
HeroFlightDirection
flightType
,
bool
isUserGestureTransition
,
bool
isUserGestureTransition
,
)
{
)
{
if
(
toRoute
!=
fromRoute
&&
toRoute
is
PageRoute
<
dynamic
>
&&
fromRoute
is
PageRoute
<
dynamic
>)
{
if
(
toRoute
==
fromRoute
||
toRoute
is
!
PageRoute
<
dynamic
>
||
fromRoute
is
!
PageRoute
<
dynamic
>)
{
return
;
}
final
PageRoute
<
dynamic
>
from
=
fromRoute
;
final
PageRoute
<
dynamic
>
from
=
fromRoute
;
final
PageRoute
<
dynamic
>
to
=
toRoute
;
final
PageRoute
<
dynamic
>
to
=
toRoute
;
...
@@ -880,11 +885,13 @@ class HeroController extends NavigatorObserver {
...
@@ -880,11 +885,13 @@ class HeroController extends NavigatorObserver {
to
.
offstage
=
to
.
animation
!.
value
==
0.0
;
to
.
offstage
=
to
.
animation
!.
value
==
0.0
;
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
if
(
from
.
navigator
==
null
||
to
.
navigator
==
null
)
{
return
;
}
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
});
});
}
}
}
}
}
// Find the matching pairs of heroes in from and to and either start or a new
// Find the matching pairs of heroes in from and to and either start or a new
// hero flight, or divert an existing one.
// hero flight, or divert an existing one.
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
ea5cfe09
...
@@ -2786,6 +2786,7 @@ class Navigator extends StatefulWidget {
...
@@ -2786,6 +2786,7 @@ class Navigator extends StatefulWidget {
// \ |
// \ |
// dispose*
// dispose*
// |
// |
// disposing
// |
// |
// disposed
// disposed
// |
// |
...
@@ -2821,6 +2822,9 @@ enum _RouteLifecycle {
...
@@ -2821,6 +2822,9 @@ enum _RouteLifecycle {
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
// routes that are completely removed from the navigator and overlay.
// routes that are completely removed from the navigator and overlay.
dispose
,
// we will dispose the route momentarily
dispose
,
// we will dispose the route momentarily
disposing
,
// The entry is waiting for its widget subtree to be disposed
// first. It is stored in _entryWaitingForSubTreeDisposal while
// awaiting that.
disposed
,
// we have disposed the route
disposed
,
// we have disposed the route
}
}
...
@@ -3047,9 +3051,26 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -3047,9 +3051,26 @@ class _RouteEntry extends RouteTransitionRecord {
currentState
=
_RouteLifecycle
.
dispose
;
currentState
=
_RouteLifecycle
.
dispose
;
}
}
void
dispose
()
{
/// Disposes this route entry and its [route] immediately.
///
/// This method does not wait for the widget subtree of the [route] to unmount
/// before disposing.
void
forcedDispose
()
{
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposed
.
index
);
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposed
.
index
);
currentState
=
_RouteLifecycle
.
disposed
;
currentState
=
_RouteLifecycle
.
disposed
;
route
.
dispose
();
}
/// Disposes this route entry and its [route].
///
/// This method waits for the widget subtree of the [route] to unmount before
/// disposing. If subtree is already unmounted, this method calls
/// [forcedDispose] immediately.
///
/// Use [forcedDispose] if the [route] need to be disposed immediately.
void
dispose
()
{
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposing
.
index
);
currentState
=
_RouteLifecycle
.
disposing
;
// If the overlay entries are still mounted, widgets in the route's subtree
// If the overlay entries are still mounted, widgets in the route's subtree
// may still reference resources from the route and we delay disposal of
// may still reference resources from the route and we delay disposal of
...
@@ -3060,10 +3081,14 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -3060,10 +3081,14 @@ class _RouteEntry extends RouteTransitionRecord {
final
Iterable
<
OverlayEntry
>
mountedEntries
=
route
.
overlayEntries
.
where
((
OverlayEntry
e
)
=>
e
.
mounted
);
final
Iterable
<
OverlayEntry
>
mountedEntries
=
route
.
overlayEntries
.
where
((
OverlayEntry
e
)
=>
e
.
mounted
);
if
(
mountedEntries
.
isEmpty
)
{
if
(
mountedEntries
.
isEmpty
)
{
route
.
dispose
();
forcedDispose
();
}
else
{
return
;
}
int
mounted
=
mountedEntries
.
length
;
int
mounted
=
mountedEntries
.
length
;
assert
(
mounted
>
0
);
assert
(
mounted
>
0
);
final
NavigatorState
navigator
=
route
.
_navigator
!;
navigator
.
_entryWaitingForSubTreeDisposal
.
add
(
this
);
for
(
final
OverlayEntry
entry
in
mountedEntries
)
{
for
(
final
OverlayEntry
entry
in
mountedEntries
)
{
late
VoidCallback
listener
;
late
VoidCallback
listener
;
listener
=
()
{
listener
=
()
{
...
@@ -3073,13 +3098,28 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -3073,13 +3098,28 @@ class _RouteEntry extends RouteTransitionRecord {
entry
.
removeListener
(
listener
);
entry
.
removeListener
(
listener
);
if
(
mounted
==
0
)
{
if
(
mounted
==
0
)
{
assert
(
route
.
overlayEntries
.
every
((
OverlayEntry
e
)
=>
!
e
.
mounted
));
assert
(
route
.
overlayEntries
.
every
((
OverlayEntry
e
)
=>
!
e
.
mounted
));
route
.
dispose
();
// This is a listener callback of one of the overlayEntries in this
// route. Disposing the route also disposes its overlayEntries and
// violates the rule that a change notifier can't be disposed during
// its notifying callback.
//
// Use a microtask to ensure the overlayEntries have finished
// notifying their listeners before disposing.
return
scheduleMicrotask
(()
{
if
(!
navigator
.
_entryWaitingForSubTreeDisposal
.
remove
(
this
))
{
// This route must have been destroyed as a result of navigator
// force dispose.
assert
(
route
.
_navigator
==
null
&&
!
navigator
.
mounted
);
return
;
}
assert
(
currentState
==
_RouteLifecycle
.
disposing
);
forcedDispose
();
});
}
}
};
};
entry
.
addListener
(
listener
);
entry
.
addListener
(
listener
);
}
}
}
}
}
bool
get
willBePresent
{
bool
get
willBePresent
{
return
currentState
.
index
<=
_RouteLifecycle
.
idle
.
index
&&
return
currentState
.
index
<=
_RouteLifecycle
.
idle
.
index
&&
...
@@ -3257,6 +3297,15 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
...
@@ -3257,6 +3297,15 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
,
RestorationMixin
{
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
,
RestorationMixin
{
late
GlobalKey
<
OverlayState
>
_overlayKey
;
late
GlobalKey
<
OverlayState
>
_overlayKey
;
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
/// A set for entries that are waiting to dispose until their subtrees are
/// disposed.
///
/// These entries are not considered to be in the _history and will usually
/// remove themselves from this set once they can dispose.
///
/// The navigator keep track of these entries so that, in case the navigator
/// itself is disposed, it can dispose these entries immediately.
final
Set
<
_RouteEntry
>
_entryWaitingForSubTreeDisposal
=
<
_RouteEntry
>{};
final
_HistoryProperty
_serializableHistory
=
_HistoryProperty
();
final
_HistoryProperty
_serializableHistory
=
_HistoryProperty
();
final
Queue
<
_NavigatorObservation
>
_observedRouteAdditions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteAdditions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteDeletions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteDeletions
=
Queue
<
_NavigatorObservation
>();
...
@@ -3338,9 +3387,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3338,9 +3387,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
registerForRestoration
(
_serializableHistory
,
'history'
);
registerForRestoration
(
_serializableHistory
,
'history'
);
// Delete everything in the old history and clear the overlay.
// Delete everything in the old history and clear the overlay.
while
(
_history
.
isNotEmpty
)
{
_forcedDisposeAllRouteEntries
();
_history
.
removeLast
().
dispose
();
}
assert
(
_history
.
isEmpty
);
assert
(
_history
.
isEmpty
);
_overlayKey
=
GlobalKey
<
OverlayState
>();
_overlayKey
=
GlobalKey
<
OverlayState
>();
...
@@ -3423,6 +3470,28 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3423,6 +3470,28 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
}
}
}
}
/// Dispose all lingering router entries immediately.
void
_forcedDisposeAllRouteEntries
()
{
_entryWaitingForSubTreeDisposal
.
removeWhere
((
_RouteEntry
entry
)
{
entry
.
forcedDispose
();
return
true
;
});
while
(
_history
.
isNotEmpty
)
{
_disposeRouteEntry
(
_history
.
removeLast
(),
graceful:
false
);
}
}
static
void
_disposeRouteEntry
(
_RouteEntry
entry
,
{
required
bool
graceful
})
{
for
(
final
OverlayEntry
overlayEntry
in
entry
.
route
.
overlayEntries
)
{
overlayEntry
.
remove
();
}
if
(
graceful
)
{
entry
.
dispose
();
}
else
{
entry
.
forcedDispose
();
}
}
void
_updateHeroController
(
HeroController
?
newHeroController
)
{
void
_updateHeroController
(
HeroController
?
newHeroController
)
{
if
(
_heroControllerFromScope
!=
newHeroController
)
{
if
(
_heroControllerFromScope
!=
newHeroController
)
{
if
(
newHeroController
!=
null
)
{
if
(
newHeroController
!=
null
)
{
...
@@ -3595,9 +3664,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3595,9 +3664,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
}());
}());
_updateHeroController
(
null
);
_updateHeroController
(
null
);
focusNode
.
dispose
();
focusNode
.
dispose
();
for
(
final
_RouteEntry
entry
in
_history
)
{
_forcedDisposeAllRouteEntries
();
entry
.
dispose
();
}
_rawNextPagelessRestorationScopeId
.
dispose
();
_rawNextPagelessRestorationScopeId
.
dispose
();
_serializableHistory
.
dispose
();
_serializableHistory
.
dispose
();
userGestureInProgressNotifier
.
dispose
();
userGestureInProgressNotifier
.
dispose
();
...
@@ -4022,6 +4089,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -4022,6 +4089,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
// Delay disposal until didChangeNext/didChangePrevious have been sent.
// Delay disposal until didChangeNext/didChangePrevious have been sent.
toBeDisposed
.
add
(
_history
.
removeAt
(
index
));
toBeDisposed
.
add
(
_history
.
removeAt
(
index
));
entry
=
next
;
entry
=
next
;
case
_RouteLifecycle
.
disposing
:
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
staging
:
case
_RouteLifecycle
.
staging
:
assert
(
false
);
assert
(
false
);
...
@@ -4051,10 +4119,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -4051,10 +4119,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
// Lastly, removes the overlay entries of all marked entries and disposes
// Lastly, removes the overlay entries of all marked entries and disposes
// them.
// them.
for
(
final
_RouteEntry
entry
in
toBeDisposed
)
{
for
(
final
_RouteEntry
entry
in
toBeDisposed
)
{
for
(
final
OverlayEntry
overlayEntry
in
entry
.
route
.
overlayEntries
)
{
_disposeRouteEntry
(
entry
,
graceful:
true
);
overlayEntry
.
remove
();
}
entry
.
dispose
();
}
}
if
(
rearrangeOverlay
)
{
if
(
rearrangeOverlay
)
{
overlay
?.
rearrange
(
_allRouteOverlayEntries
);
overlay
?.
rearrange
(
_allRouteOverlayEntries
);
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
ea5cfe09
...
@@ -84,6 +84,9 @@ abstract class OverlayRoute<T> extends Route<T> {
...
@@ -84,6 +84,9 @@ abstract class OverlayRoute<T> extends Route<T> {
@override
@override
void
dispose
()
{
void
dispose
()
{
for
(
final
OverlayEntry
entry
in
_overlayEntries
)
{
entry
.
dispose
();
}
_overlayEntries
.
clear
();
_overlayEntries
.
clear
();
super
.
dispose
();
super
.
dispose
();
}
}
...
@@ -704,7 +707,9 @@ mixin LocalHistoryRoute<T> on Route<T> {
...
@@ -704,7 +707,9 @@ mixin LocalHistoryRoute<T> on Route<T> {
// elements during finalizeTree. The state is locked at this moment, and
// elements during finalizeTree. The state is locked at this moment, and
// we can only notify state has changed in the next frame.
// we can only notify state has changed in the next frame.
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
duration
)
{
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
duration
)
{
if
(
isActive
)
{
changedInternalState
();
changedInternalState
();
}
});
});
}
else
{
}
else
{
changedInternalState
();
changedInternalState
();
...
...
packages/flutter/test/material/about_test.dart
View file @
ea5cfe09
...
@@ -427,7 +427,7 @@ void main() {
...
@@ -427,7 +427,7 @@ void main() {
final
FakeLicenseEntry
licenseEntry
=
FakeLicenseEntry
();
final
FakeLicenseEntry
licenseEntry
=
FakeLicenseEntry
();
licenseCompleter
.
complete
(
licenseEntry
);
licenseCompleter
.
complete
(
licenseEntry
);
expect
(
licenseEntry
.
packagesCalled
,
false
);
expect
(
licenseEntry
.
packagesCalled
,
false
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage returns late if unmounted'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage returns late if unmounted'
,
(
WidgetTester
tester
)
async
{
final
Completer
<
LicenseEntry
>
licenseCompleter
=
Completer
<
LicenseEntry
>();
final
Completer
<
LicenseEntry
>
licenseCompleter
=
Completer
<
LicenseEntry
>();
...
@@ -452,7 +452,7 @@ void main() {
...
@@ -452,7 +452,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
expect
(
licenseEntry
.
packagesCalled
,
true
);
expect
(
licenseEntry
.
packagesCalled
,
true
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage logic defaults to executable name for app name'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage logic defaults to executable name for app name'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
...
@@ -1128,7 +1128,7 @@ void main() {
...
@@ -1128,7 +1128,7 @@ void main() {
// Configure to show the default layout.
// Configure to show the default layout.
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage master view layout position - rtl'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage master view layout position - rtl'
,
(
WidgetTester
tester
)
async
{
const
TextDirection
textDirection
=
TextDirection
.
rtl
;
const
TextDirection
textDirection
=
TextDirection
.
rtl
;
...
@@ -1191,7 +1191,7 @@ void main() {
...
@@ -1191,7 +1191,7 @@ void main() {
// Configure to show the default layout.
// Configure to show the default layout.
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'License page title in lateral UI does not use AppBarTheme.foregroundColor'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'License page title in lateral UI does not use AppBarTheme.foregroundColor'
,
(
WidgetTester
tester
)
async
{
// This is a regression test for https://github.com/flutter/flutter/issues/108991
// This is a regression test for https://github.com/flutter/flutter/issues/108991
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
ea5cfe09
...
@@ -4171,7 +4171,7 @@ class RouteAnnouncementSpy extends Route<void> {
...
@@ -4171,7 +4171,7 @@ class RouteAnnouncementSpy extends Route<void> {
final
AnnouncementCallBack
?
onDidPopNext
;
final
AnnouncementCallBack
?
onDidPopNext
;
@override
@override
List
<
OverlayEntry
>
get
overlayEntries
=>
<
OverlayEntry
>[
final
List
<
OverlayEntry
>
overlayEntries
=
<
OverlayEntry
>[
OverlayEntry
(
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
const
Placeholder
(),
builder:
(
BuildContext
context
)
=>
const
Placeholder
(),
),
),
...
...
packages/flutter/test/widgets/routes_test.dart
View file @
ea5cfe09
...
@@ -401,7 +401,7 @@ void main() {
...
@@ -401,7 +401,7 @@ void main() {
],
],
);
);
await
tester
.
pumpWidget
(
Container
());
await
tester
.
pumpWidget
(
Container
());
expect
(
results
,
equals
(<
String
>[
'
A: dispose'
,
'b
: dispose'
]));
expect
(
results
,
equals
(<
String
>[
'
b: dispose'
,
'A
: dispose'
]));
expect
(
routes
.
isEmpty
,
isTrue
);
expect
(
routes
.
isEmpty
,
isTrue
);
results
.
clear
();
results
.
clear
();
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment