flutterdartroutes

Processing the system back button in dart/flutter


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');
 },

Solution

  • 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