Unverified Commit 35c52e7b authored by hangyu's avatar hangyu Committed by GitHub

Handle the case when _CupertinoBackGestureDetector is disposed during the drag. (#139585)

Fix #137033

This happens when go_router updates the navigator pages during a user
gesture.


## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat

---------
Co-authored-by: 's avatarchunhtai <47866232+chunhtai@users.noreply.github.com>
parent 948523b8
......@@ -626,6 +626,16 @@ class _CupertinoBackGestureDetectorState<T> extends State<_CupertinoBackGestureD
@override
void dispose() {
_recognizer.dispose();
// If this is disposed during a drag, call navigator.didStopUserGesture.
if (_backGestureController != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_backGestureController?.navigator.mounted ?? false) {
_backGestureController?.navigator.didStopUserGesture();
}
_backGestureController = null;
});
}
super.dispose();
}
......
......@@ -758,6 +758,40 @@ void main() {
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(300));
});
// Regression test for https://github.com/flutter/flutter/issues/137033.
testWidgetsWithLeakTracking('Update pages during a drag gesture will not stuck', (WidgetTester tester) async {
await tester.pumpWidget(const _TestPageUpdate());
// Tap this button will update the pages in two seconds.
await tester.tap(find.text('Update Pages'));
await tester.pump();
// Start swiping.
final TestGesture swipeGesture = await tester.startGesture(const Offset(5, 100));
await swipeGesture.moveBy(const Offset(100, 0));
await tester.pump();
expect(
tester.stateList<NavigatorState>(find.byType(Navigator)).last.userGestureInProgress,
true,
);
// Wait for pages to update.
await tester.pump(const Duration(seconds: 3));
// Verify pages are updated.
expect(
find.text('New page'),
findsOneWidget,
);
// Verify `userGestureInProgress` is set to false.
expect(
tester.stateList<NavigatorState>(find.byType(Navigator)).last.userGestureInProgress,
false,
);
});
testWidgetsWithLeakTracking('Pop gesture snapping is not linear', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
......@@ -2247,6 +2281,68 @@ Widget buildNavigator({
}
// A test target to updating pages in navigator.
//
// It contains 3 routes:
//
// * The initial route, 'home'.
// * The 'old' route, displays a button showing 'Update pages'. Tap the button
// will update pages.
// * The 'new' route, displays the new page.
class _TestPageUpdate extends StatefulWidget {
const _TestPageUpdate();
@override
State<StatefulWidget> createState() => _TestPageUpdateState();
}
class _TestPageUpdateState extends State<_TestPageUpdate> {
bool updatePages = false;
@override
Widget build(BuildContext context) {
final GlobalKey<State<StatefulWidget>> navKey = GlobalKey();
return MaterialApp(
home: Navigator(
key: navKey,
pages: updatePages
? <Page<dynamic>>[
const CupertinoPage<dynamic>(name: '/home', child: Text('home')),
const CupertinoPage<dynamic>(name: '/home/new', child: Text('New page')),
]
: <Page<dynamic>>[
const CupertinoPage<dynamic>(name: '/home', child: Text('home')),
CupertinoPage<dynamic>(name: '/home/old', child: buildMainPage()),
],
onPopPage: (_, __) {
return false;
},
),
);
}
Widget buildMainPage() {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Main'),
ElevatedButton(
onPressed: () {
Future<void>.delayed(const Duration(seconds: 2), () {
setState(() {
updatePages = true;
});
});
},
child: const Text('Update Pages'),
),
],
),
),
);
}
}
// A test target for post-route cancel events.
//
// It contains 2 routes:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment