skip to Main Content

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

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. First, You have to describe all routes in main.dart. First you have to create routes.

    Map<String, WidgetBuilder> routes() {
      return {
        'splash_screen': (context) => const SplashScreenView(),
        'menu_view': (context) => const MenuView(),
      };
    }
    
    Route<dynamic>? generateRoute(settings) {
      switch (settings.name) {
        case '/':
          return MaterialPageRoute(builder: (context) => const SplashScreenView());
        case '/splash_screen':
          return MaterialPageRoute(builder: (context) => const SplashScreenView());
        case '/menu_view':
          return MaterialPageRoute(
              builder: (context) => const MenuView());
        default:
          return MaterialPageRoute(builder: (context) => const SplashScreenView());
      }
    }
    

    And then in your main.dart, you can add routes like this

    MaterialApp(
            debugShowCheckedModeBanner: false,
            routes: routes(),
            onGenerateRoute: generateRoute,
            home: const SplashScreenView(),
          )
    

    If you want to use bottom navigation bar, you have to add route of navigation bottom page to the routes, and you can add your nested pages for navigation bottom bar, inside the navigation bottom bar page

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search