I just recently started studying Dart / Flutter and ran into a problem: the system back button on android does not return to the previous screen, instead it closes the application.
When I was looking at ways to create nested pages, I wrote the following code:
// settings_navigator.dart
class SettingsNavigator extends StatelessWidget {
const SettingsNavigator({super.key});
@override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (settings) {
switch (settings.name) {
case '/notifications':
return UnanimatedPageRoute(
builder: (context) => const NotificationsScreen(),
);
default:
return MaterialPageRoute(
builder: (context) => const SettingsScreen(),
);
}
},
);
}
}
This approach was due to the fact that I wanted to place the bottom menu on all screens, however, with the standard approach, it was overlapped by a nested page
In my code, "default" is responsible for the parent page, and "case '/notifications'" for the nested one.
On the "notification" subpage, I have an appbar:
return AppBar(
titleSpacing: 0,
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(title),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
);
As you can see, it is used here Navigator.pop(context); I tried using PopScope, but it didn't lead to anything:
class SettingsNavigator extends StatelessWidget {
const SettingsNavigator({super.key});
@override
Widget build(BuildContext context) {
return PopScope(
onPopInvokedWithResult: (didPop, result) {
Navigator.pop(context);
},
child: Navigator(
onGenerateRoute: (settings) {
switch (settings.name) {
case '/notifications':
return UnanimatedPageRoute(
builder: (context) => const NotificationsScreen(),
);
default:
return MaterialPageRoute(
builder: (context) => const SettingsScreen(),
);
}
},
),
);
}
}
I assume this behavior is related to my route architecture, but I have not found meaningful information on the Internet with step-by-step creation of applications with nested pages.
I will also give the main.dart using the recommendation from the user "Handelika" main.dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Schedule App',
theme: AppConfig.themeData(),
localizationsDelegates: AppConfig.localizationsDelegates,
supportedLocales: AppConfig.getSupportedLocales(),
locale: AppConfig.getDefaultLocale(),
home: const MainScreen(),
routes: routes(),
onGenerateRoute: generateRoute,
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
final List<Widget> _pages = [
const ScheduleNavigator(),
const EditNavigator(),
const SettingsNavigator(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigation(
selectedIndex: _selectedIndex,
onItemTapped: _onItemTapped,
),
);
}
}
Map<String, WidgetBuilder> routes() {
return {
'schedule': (context) => const ScheduleNavigator(),
'edit': (context) => const EditNavigator(),
'settings': (context) => const SettingsNavigator(),
'/notifications': (context) => const NotificationsScreen(),
};
}
Route<dynamic>? generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => const ScheduleNavigator());
case '/schedule':
return MaterialPageRoute(builder: (context) => const ScheduleNavigator());
case '/edit':
return MaterialPageRoute(builder: (context) => const EditNavigator());
case '/settings':
return MaterialPageRoute(builder: (context) => const SettingsNavigator());
case '/notifications':
return MaterialPageRoute(
builder: (context) => const NotificationsScreen());
default:
return MaterialPageRoute(builder: (context) => const ScheduleNavigator());
}
}
and new settings_navigator
class SettingsNavigator extends StatelessWidget {
const SettingsNavigator({super.key});
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: '/settings',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/notifications':
return MaterialPageRoute(
builder: (context) => const NotificationsScreen());
default:
return MaterialPageRoute(
builder: (context) => const SettingsScreen());
}
},
);
}
}
also settings_screen:
onTap: () {
Navigator.pushNamed(context, '/notifications');
},
All I realized was that it's useful to use pre-built solutions. To fix my problem, I just used the "go_router" package:
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorScheduleKey =
GlobalKey<NavigatorState>(debugLabel: 'shellSchedule');
final _shellNavigatorEditKey =
GlobalKey<NavigatorState>(debugLabel: 'shellEdit');
final _shellNavigatorSettingsKey =
GlobalKey<NavigatorState>(debugLabel: 'shellSettings');
final goRouter = GoRouter(
initialLocation: '/schedule',
navigatorKey: _rootNavigatorKey,
debugLogDiagnostics: true,
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNestedNavigation(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
navigatorKey: _shellNavigatorScheduleKey,
routes: [
GoRoute(
path: '/',
builder: (context, state) => const ScheduleScreen(),
),
GoRoute(
path: '/schedule',
pageBuilder: (context, state) => const NoTransitionPage(
child: ScheduleScreen(),
),
routes: [
GoRoute(
path: 'lesson',
builder: (context, state) => const LessonScreen(),
),
],
),
],
),
StatefulShellBranch(
navigatorKey: _shellNavigatorEditKey,
routes: [
GoRoute(
path: '/edit',
pageBuilder: (context, state) => const NoTransitionPage(
child: EditScreen(),
),
),
],
),
StatefulShellBranch(
navigatorKey: _shellNavigatorSettingsKey,
routes: [
GoRoute(
path: '/settings',
pageBuilder: (context, state) => const NoTransitionPage(
child: SettingsScreen(),
),
routes: [
GoRoute(
path: 'appearance',
builder: (context, state) => const AppearanceScreen(),
),
GoRoute(
path: 'notifications',
builder: (context, state) => const NotificationsScreen(),
),
GoRoute(
path: 'qr',
builder: (context, state) => const QrScreen(),
),
],
),
],
),
],
),
],
);
Special thanks to the author of the article for the solution method