Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5cb2c4f

Browse filesBrowse files
Check if route is laid out before hero
1 parent ed68dd8 commit 5cb2c4f
Copy full SHA for 5cb2c4f

File tree

Expand file treeCollapse file tree

2 files changed

+117
-7
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+117
-7
lines changed

‎packages/flutter/lib/src/widgets/heroes.dart

Copy file name to clipboardExpand all lines: packages/flutter/lib/src/widgets/heroes.dart
+15-7Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -677,11 +677,15 @@ class _HeroFlight {
677677
final HeroFlightDirection type = initialManifest.type;
678678
switch (type) {
679679
case HeroFlightDirection.pop:
680-
return initial.value == 1.0 && initialManifest.isUserGestureTransition
680+
return initialManifest.isUserGestureTransition
681681
// During user gesture transitions, the animation controller isn't
682-
// driving the reverse transition, but should still be in a previously
683-
// completed stage with the initial value at 1.0.
684-
? initial.status == AnimationStatus.completed
682+
// driving the reverse transition, but should have a starting value
683+
// approaching 1.0. In cases where the toRoute begane offstage, there
684+
// is a slight delay while the toRoute laysout before the hero's
685+
// flight begins, so the animation may not begin completed.
686+
? initial.value == 1.0
687+
? initial.status == AnimationStatus.completed
688+
: initial.status == AnimationStatus.forward
685689
: initial.status == AnimationStatus.reverse;
686690
case HeroFlightDirection.push:
687691
return initial.value == 0.0 && initial.status == AnimationStatus.forward;
@@ -923,8 +927,13 @@ class HeroController extends NavigatorObserver {
923927

924928
// For pop transitions driven by a user gesture: if the "to" page has
925929
// maintainState = true, then the hero's final dimensions can be measured
926-
// immediately because their page's layout is still valid.
927-
if (isUserGestureTransition && flightType == HeroFlightDirection.pop && toRoute.maintainState) {
930+
// immediately because their page's layout is still valid. Unless due to directly
931+
// adding routes to the pages stack caused the route to never get laid out.
932+
final bool needsLayout = toRoute.subtreeContext?.findRenderObject()?.debugNeedsLayout ?? true;
933+
if (isUserGestureTransition &&
934+
flightType == HeroFlightDirection.pop &&
935+
toRoute.maintainState &&
936+
!needsLayout) {
928937
_startHeroTransition(fromRoute, toRoute, flightType, isUserGestureTransition);
929938
} else {
930939
// Otherwise, delay measuring until the end of the next frame to allow
@@ -934,7 +943,6 @@ class HeroController extends NavigatorObserver {
934943
// frame completes, we'll know where the heroes in the `to` route are
935944
// going to end up, and the `to` route will go back onstage.
936945
toRoute.offstage = toRoute.animation!.value == 0.0;
937-
938946
WidgetsBinding.instance.addPostFrameCallback((Duration value) {
939947
if (fromRoute.navigator == null || toRoute.navigator == null) {
940948
return;

‎packages/flutter/test/widgets/heroes_test.dart

Copy file name to clipboardExpand all lines: packages/flutter/test/widgets/heroes_test.dart
+102Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,73 @@ class MyStatefulWidgetState extends State<MyStatefulWidget> {
216216
Widget build(BuildContext context) => Text(widget.value);
217217
}
218218

219+
class DeepLinkApp extends StatefulWidget {
220+
const DeepLinkApp({super.key});
221+
222+
static const CupertinoPage<void> _homeScreen = CupertinoPage<void>(
223+
name: '/',
224+
child: CupertinoPageScaffold(
225+
navigationBar: CupertinoNavigationBar(middle: Text('First')),
226+
child: Center(child: Text('Home Screen')),
227+
),
228+
);
229+
static const CupertinoPage<void> _middleScreen = CupertinoPage<void>(
230+
name: '/middle',
231+
child: CupertinoPageScaffold(
232+
navigationBar: CupertinoNavigationBar(middle: Text('Second')),
233+
child: Center(child: Text('Middle Screen')),
234+
),
235+
);
236+
static const CupertinoPage<void> _lastScreen = CupertinoPage<void>(
237+
name: '/middle/last',
238+
child: CupertinoPageScaffold(
239+
navigationBar: CupertinoNavigationBar(middle: Text('Third')),
240+
child: Center(child: Text('Last Screen')),
241+
),
242+
);
243+
244+
@override
245+
DeepLinkAppState createState() => DeepLinkAppState();
246+
}
247+
248+
class DeepLinkAppState extends State<DeepLinkApp> {
249+
late List<Page<dynamic>> _pages;
250+
251+
@override
252+
void initState() {
253+
super.initState();
254+
_pages = <Page<dynamic>>[DeepLinkApp._homeScreen];
255+
}
256+
257+
void goToDeepScreen() {
258+
setState(() {
259+
_pages = <Page<dynamic>>[
260+
DeepLinkApp._homeScreen,
261+
DeepLinkApp._middleScreen,
262+
DeepLinkApp._lastScreen,
263+
];
264+
});
265+
}
266+
267+
@override
268+
Widget build(BuildContext context) {
269+
return CupertinoApp(
270+
builder: (BuildContext context, Widget? child) {
271+
return Navigator(
272+
pages: _pages,
273+
onDidRemovePage: (Page<Object?> page) {
274+
setState(() {
275+
if (_pages.length > 1) {
276+
_pages = List<Page<dynamic>>.from(_pages)..removeLast();
277+
}
278+
});
279+
},
280+
);
281+
},
282+
);
283+
}
284+
}
285+
219286
Future<void> main() async {
220287
final ui.Image testImage = await createTestImage();
221288

@@ -2984,6 +3051,41 @@ Future<void> main() async {
29843051
}),
29853052
);
29863053

3054+
// Regression test for https://github.com/flutter/flutter/issues/168267.
3055+
testWidgets('Check if previous page is laid out on backswipe gesture before flight', (
3056+
WidgetTester tester,
3057+
) async {
3058+
final GlobalKey<DeepLinkAppState> appKey = GlobalKey();
3059+
await tester.pumpWidget(DeepLinkApp(key: appKey));
3060+
3061+
await tester.pumpAndSettle();
3062+
3063+
expect(find.text('Home Screen'), findsOneWidget);
3064+
expect(find.text('Last Screen'), findsNothing);
3065+
3066+
appKey.currentState?.goToDeepScreen();
3067+
3068+
await tester.pumpAndSettle();
3069+
3070+
expect(find.text('Home Screen'), findsNothing);
3071+
expect(find.text('Last Screen'), findsOneWidget);
3072+
3073+
final TestGesture gesture = await tester.startGesture(const Offset(0.01, 300));
3074+
3075+
await gesture.moveTo(const Offset(10, 300));
3076+
await tester.pump();
3077+
// Should not throw an assert here for size and finite space.
3078+
await gesture.moveTo(const Offset(500, 300));
3079+
await tester.pump();
3080+
3081+
await gesture.up();
3082+
await tester.pumpAndSettle();
3083+
3084+
expect(find.text('Home Screen'), findsNothing);
3085+
expect(find.text('Middle Screen'), findsOneWidget);
3086+
expect(find.text('Last Screen'), findsNothing);
3087+
});
3088+
29873089
// Regression test for https://github.com/flutter/flutter/issues/40239.
29883090
testWidgets(
29893091
'In a pop transition, when fromHero is null, the to hero should eventually become visible',

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.