-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to reproduce
Imagine a routing structure with two nested Navigators, where the root Navigator can show a route on top of the nested Navigator (what I'm calling interleaving). When performing a back gesture on Android on that top route, the route underneath, in the nested Navigator, will be popped instead of the top route on the root Navigator.
- Run an app with interleaving, such as the one given below, on an Android device with predictive back enabled.
- Navigate to the final page, which is the final route of the root Navigator.
- Perform a back gesture.
Expected results
The visible route shows the predictive back route transition and is popped.
Actual results
Nothing happens (actually, the route underneath, which is the final route of the nested Navigator, is popped). Performing another back gesture will go back to the first route of the nested Navigator.
Code sample
Code sample
import 'package:flutter/material.dart';
void main() async {
runApp(const _MyApp());
}
class _MyApp extends StatelessWidget {
const _MyApp();
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: false,
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
},
),
),
title: 'Flutter Demo',
routes: {
'/': (BuildContext context) {
return _NestedNavigatorPage();
},
'nav1leaf': (BuildContext context) {
return const _Nav1LeafPage();
},
},
);
}
}
class _NestedNavigatorPage extends StatelessWidget {
_NestedNavigatorPage();
final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return Navigator(
key: _nestedNavigatorKey,
initialRoute: 'nav2home',
onGenerateRoute: (RouteSettings settings) {
return switch (settings.name) {
'nav2home' => MaterialPageRoute(
builder: (BuildContext context) => const _Nav2HomePage(),
),
'nav2leaf' => MaterialPageRoute(
builder: (BuildContext context) => const _Nav2LeafPage(),
),
_ => MaterialPageRoute(
builder: (BuildContext context) => const Text('404'),
),
};
},
);
}
}
class _Nav2HomePage extends StatelessWidget {
const _Nav2HomePage();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Home page of Navigator 2'),
const SizedBox(height: 16, width: double.infinity),
ElevatedButton(
child: const Text('Open nav 2 leaf page'),
onPressed: () => Navigator.of(context).pushNamed('nav2leaf'),
),
],
),
),
);
}
}
class _Nav2LeafPage extends StatelessWidget {
const _Nav2LeafPage();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Leaf page of Navigator 2'),
const SizedBox(height: 16, width: double.infinity),
ElevatedButton(
child: const Text('Open nav 1 leaf page'),
onPressed: () => Navigator.of(context, rootNavigator: true).pushNamed('nav1leaf'),
),
],
),
),
);
}
}
class _Nav1LeafPage extends StatelessWidget {
const _Nav1LeafPage();
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Leaf of Navigator 1'),
],
),
);
}
}
Screenshots or Video
Video demonstration
Screencast.from.2024-07-30.13-30-36.webm
Logs
No response
Flutter Doctor output
Doctor output
[!] Flutter (Channel [user-branch], 3.22.0-35.0.pre.889, on Debian GNU/Linux rodete 6.6.15-2rodete2-amd64, locale en_US.UTF-8)
! Flutter version 3.22.0-35.0.pre.889 on channel [user-branch] at /usr/local/google/home/jmccandless/Projects/flutter
Currently on an unknown channel. Run `flutter channel` to switch to an official channel.
If that doesn't fix the issue, reinstall Flutter by following instructions at https://flutter.dev/setup.
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2023.1)
[✓] Android Studio (version 2023.2)
[✓] Connected device (2 available)
[✓] Network resources
! Doctor found issues in 1 category.
Why does this happen?
This bug is reproduced only when using the PredictiveBackPageTransitionsBuilder and not with any other transition. The reason is that when using any other transition, the back gesture is sent via handlePopRoute and handled by WidgetsApp. WidgetsApp calls maybePop on the root Navigator (which might be caught by PopScope which then pops the nested Navigator, if needed). This all works.
However with predictive back, it's sent via startBackGesture and handled by _PredictiveBackGestureDetector. This seems to call handleStartBack on the _PredictiveBackGestureDetector for the nested Navigator, not the root one.
How can it be fixed
Maybe _PredictiveBackGestureDetector.handleStartBack needs to be smart enough to return false when it's not the topmost route? It might be hard to determine that in a nested Navigator scenario, though.