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
27e1efc1
Unverified
Commit
27e1efc1
authored
Oct 26, 2020
by
Michael Goderbauer
Committed by
GitHub
Oct 26, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Delay Route disposal until OverlayEntries are unmounted (#68913)
parent
e7b66ac7
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
155 additions
and
14 deletions
+155
-14
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+30
-9
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+29
-1
overlay.dart
packages/flutter/lib/src/widgets/overlay.dart
+30
-1
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+0
-1
route_test.dart
packages/flutter/test/cupertino/route_test.dart
+51
-0
routes_test.dart
packages/flutter/test/widgets/routes_test.dart
+15
-2
No files found.
packages/flutter/lib/src/widgets/heroes.dart
View file @
27e1efc1
...
...
@@ -534,15 +534,7 @@ class _HeroFlight {
);
}
void
_handleAnimationUpdate
(
AnimationStatus
status
)
{
// The animation will not finish until the user lifts their finger, so we
// should ignore the status update if the gesture is in progress.
//
// This also relies on the animation to update its status at the end of the
// gesture. See the _CupertinoBackGestureController.dragEnd for how
// cupertino page route achieves that.
if
(
manifest
!.
fromRoute
.
navigator
?.
userGestureInProgress
==
true
)
return
;
void
_performAnimationUpdate
(
AnimationStatus
status
)
{
if
(
status
==
AnimationStatus
.
completed
||
status
==
AnimationStatus
.
dismissed
)
{
_proxyAnimation
.
parent
=
null
;
...
...
@@ -560,6 +552,35 @@ class _HeroFlight {
}
}
bool
_scheduledPerformAnimtationUpdate
=
false
;
void
_handleAnimationUpdate
(
AnimationStatus
status
)
{
// The animation will not finish until the user lifts their finger, so we
// should suppress the status update if the gesture is in progress, and
// delay it until the finger is lifted.
if
(
manifest
!.
fromRoute
.
navigator
?.
userGestureInProgress
!=
true
)
{
_performAnimationUpdate
(
status
);
return
;
}
if
(
_scheduledPerformAnimtationUpdate
)
return
;
// The `navigator` must be non-null here, or the first if clause above would
// have returned from this method.
final
NavigatorState
navigator
=
manifest
!.
fromRoute
.
navigator
!;
void
delayedPerformAnimtationUpdate
()
{
assert
(!
navigator
.
userGestureInProgress
);
assert
(
_scheduledPerformAnimtationUpdate
);
_scheduledPerformAnimtationUpdate
=
false
;
navigator
.
userGestureInProgressNotifier
.
removeListener
(
delayedPerformAnimtationUpdate
);
_performAnimationUpdate
(
_proxyAnimation
.
status
);
}
assert
(
navigator
.
userGestureInProgress
);
_scheduledPerformAnimtationUpdate
=
true
;
navigator
.
userGestureInProgressNotifier
.
addListener
(
delayedPerformAnimtationUpdate
);
}
// The simple case: we're either starting a push or a pop animation.
void
start
(
_HeroFlightManifest
initialManifest
)
{
assert
(!
_aborted
);
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
27e1efc1
...
...
@@ -3041,8 +3041,36 @@ class _RouteEntry extends RouteTransitionRecord {
void
dispose
()
{
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposed
.
index
);
route
.
dispose
();
currentState
=
_RouteLifecycle
.
disposed
;
// 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
// the route until the overlay entries are no longer mounted.
// Since the overlay entry is the root of the route's subtree it will only
// get unmounted after every other widget in the subtree has been unmounted.
final
Iterable
<
OverlayEntry
>
mountedEntries
=
route
.
overlayEntries
.
where
((
OverlayEntry
e
)
=>
e
.
mounted
);
if
(
mountedEntries
.
isEmpty
)
{
route
.
dispose
();
}
else
{
int
mounted
=
mountedEntries
.
length
;
assert
(
mounted
>
0
);
for
(
final
OverlayEntry
entry
in
mountedEntries
)
{
late
VoidCallback
listener
;
listener
=
()
{
assert
(
mounted
>
0
);
assert
(!
entry
.
mounted
);
mounted
--;
entry
.
removeListener
(
listener
);
if
(
mounted
==
0
)
{
assert
(
route
.
overlayEntries
.
every
((
OverlayEntry
e
)
=>
!
e
.
mounted
));
route
.
dispose
();
}
};
entry
.
addListener
(
listener
);
}
}
}
bool
get
willBePresent
{
...
...
packages/flutter/lib/src/widgets/overlay.dart
View file @
27e1efc1
...
...
@@ -45,13 +45,17 @@ import 'ticker_provider.dart';
/// if widgets in an overlay entry with [maintainState] set to true repeatedly
/// call [State.setState], the user's battery will be drained unnecessarily.
///
/// [OverlayEntry] is a [ChangeNotifier] that notifies when the widget built by
/// [builder] is mounted or unmounted, whose exact state can be queried by
/// [mounted].
///
/// See also:
///
/// * [Overlay]
/// * [OverlayState]
/// * [WidgetsApp]
/// * [MaterialApp]
class
OverlayEntry
{
class
OverlayEntry
extends
ChangeNotifier
{
/// Creates an overlay entry.
///
/// To insert the entry into an [Overlay], first find the overlay using
...
...
@@ -113,6 +117,19 @@ class OverlayEntry {
_overlay
!.
_didChangeEntryOpacity
();
}
/// Whether the [OverlayEntry] is currently mounted in the widget tree.
///
/// The [OverlayEntry] notifies its listeners when this value changes.
bool
get
mounted
=>
_mounted
;
bool
_mounted
=
false
;
void
_updateMounted
(
bool
value
)
{
if
(
value
==
_mounted
)
{
return
;
}
_mounted
=
value
;
notifyListeners
();
}
OverlayState
?
_overlay
;
final
GlobalKey
<
_OverlayEntryWidgetState
>
_key
=
GlobalKey
<
_OverlayEntryWidgetState
>();
...
...
@@ -172,6 +189,18 @@ class _OverlayEntryWidget extends StatefulWidget {
}
class
_OverlayEntryWidgetState
extends
State
<
_OverlayEntryWidget
>
{
@override
void
initState
()
{
super
.
initState
();
widget
.
entry
.
_updateMounted
(
true
);
}
@override
void
dispose
()
{
widget
.
entry
.
_updateMounted
(
false
);
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
TickerMode
(
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
27e1efc1
...
...
@@ -189,7 +189,6 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
// removing the route and disposing it.
if
(!
isActive
)
{
navigator
!.
finalizeRoute
(
this
);
assert
(
overlayEntries
.
isEmpty
);
}
break
;
}
...
...
packages/flutter/test/cupertino/route_test.dart
View file @
27e1efc1
...
...
@@ -1632,6 +1632,57 @@ void main() {
expect
(
find
.
byType
(
CupertinoButton
),
findsOneWidget
);
expect
(
find
.
text
(
'PointerCancelEvents: 1'
),
findsOneWidget
);
});
testWidgets
(
'Popping routes during back swipe should not crash'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/63984#issuecomment-675679939
final
CupertinoPageRoute
<
void
>
r
=
CupertinoPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
const
Scaffold
(
body:
Center
(
child:
Text
(
'child'
),
),
);
});
late
NavigatorState
navigator
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
RaisedButton
(
child:
const
Text
(
'Home'
),
onPressed:
()
{
navigator
=
Navigator
.
of
(
context
)!;
assert
(
navigator
!=
null
);
navigator
.
push
<
void
>(
r
);
},
);
}),
),
));
final
TestGesture
gesture
=
await
tester
.
createGesture
();
await
gesture
.
down
(
tester
.
getCenter
(
find
.
byType
(
RaisedButton
)));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
await
gesture
.
down
(
const
Offset
(
3
,
300
),
timeStamp:
Duration
.
zero
);
// Need 2 events to form a valid drag
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
await
gesture
.
moveTo
(
const
Offset
(
30
,
300
),
timeStamp:
const
Duration
(
milliseconds:
100
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
gesture
.
moveTo
(
const
Offset
(
50
,
300
),
timeStamp:
const
Duration
(
milliseconds:
200
));
// Pause a while so that the route is popped when the drag is canceled
await
tester
.
pump
(
const
Duration
(
milliseconds:
1000
));
await
gesture
.
moveTo
(
const
Offset
(
51
,
300
),
timeStamp:
const
Duration
(
milliseconds:
1200
));
// Remove the drag
navigator
.
removeRoute
(
r
);
await
tester
.
pump
();
});
}
class
MockNavigatorObserver
extends
NavigatorObserver
{
...
...
packages/flutter/test/widgets/routes_test.dart
View file @
27e1efc1
...
...
@@ -101,13 +101,16 @@ Future<void> runNavigatorTest(
WidgetTester
tester
,
NavigatorState
host
,
VoidCallback
test
,
List
<
String
>
expectations
,
)
async
{
List
<
String
>
expectations
,
[
List
<
String
>
expectationsAfterAnotherPump
=
const
<
String
>[],
])
async
{
expect
(
host
,
isNotNull
);
test
();
expect
(
results
,
equals
(
expectations
));
results
.
clear
();
await
tester
.
pump
();
expect
(
results
,
equals
(
expectationsAfterAnotherPump
));
results
.
clear
();
}
void
main
(
)
{
...
...
@@ -199,6 +202,8 @@ void main() {
<
String
>[
// stack is: initial, two
'third: didPop hello'
,
'two: didPopNext third'
,
],
<
String
>[
'third: dispose'
,
],
);
...
...
@@ -209,6 +214,8 @@ void main() {
<
String
>[
// stack is: initial
'two: didPop good bye'
,
'initial: didPopNext two'
,
],
<
String
>[
'two: dispose'
,
],
);
...
...
@@ -278,6 +285,8 @@ void main() {
<
String
>[
'third: didPop good bye'
,
'second: didPopNext third'
,
],
<
String
>[
'third: dispose'
,
],
);
...
...
@@ -320,6 +329,8 @@ void main() {
<
String
>[
'four: didPop the end'
,
'second: didPopNext four'
,
],
<
String
>[
'four: dispose'
,
],
);
...
...
@@ -395,6 +406,8 @@ void main() {
<
String
>[
'C: didPop null'
,
'b: didPopNext C'
,
],
<
String
>[
'C: dispose'
,
],
);
...
...
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