Deep Nested Navigation Flutter

94 Views Asked by At

I need help with using either Go Router or Auto Route for deep nested navigation. I can navigate just fine to say the child route of the tab bar but if I want to go to the edit profile route it will remove the previous pages. Also I seem to have an issue where if from profile route I want to view a post then select the profile picture for that post and go to the profile I get an error because its not a sub route of post even though post is a child of profile route. Does anyone have a recommended way to do this. I know in swift it would just work but I really just want to use flutter.

class NavKeys {
  final _rootNavigatorKey = GlobalKey<NavigatorState>();
  final _shellNavigatorHomeKey =
      GlobalKey<NavigatorState>(debugLabel: 'shellHome');
  final _shellNavigatorChatKey =
      GlobalKey<NavigatorState>(debugLabel: 'shellChat');
  final _shellNavigatorNotificationKey =
      GlobalKey<NavigatorState>(debugLabel: 'shellNotification');
  final _shellNavigatorLivestreamKey =
      GlobalKey<NavigatorState>(debugLabel: 'shellLivestream');
  final _shellNavigatorSearchKey =
      GlobalKey<NavigatorState>(debugLabel: 'shellSearch');
}

class PostRouter {
  GoRoute postFiles = GoRoute(
    path: RouterNames.postFilesRoute,
    builder: (context, state) {
      return CarouselFullScreen(
        key: state.pageKey,
        index: state.extra as int,
      );
    },
  );
  GoRoute reactions = GoRoute(
    path: RouterNames.reactionsViewRoute,
    builder: (context, state) {
      return ReactionListFullScreen(
        key: state.pageKey,
        isComment: state.extra as bool,
        parentId: state.uri.queryParameters['parentId'] as String,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
    ],
  );
  GoRoute postRoute = GoRoute(
    path: RouterNames.postRoute,
    builder: (context, state) {
      return PostFullView(
        key: state.pageKey,
        postId: state.pathParameters['id'] as String,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
      PostRouter().postFiles,
      PostRouter().postFiles,
      PostRouter().reactions,
    ],
  );
}

class ProfileRouter {
  GoRoute editProfile = GoRoute(
    path: RouterNames.editProfileRoute,
    builder: (context, state) {
      return EditProfile(
        key: state.pageKey,
      );
    },
  );
  GoRoute profileRoute = GoRoute(
    path: RouterNames.profileRoute,
    builder: (context, state) {
      return ProfileView(
        id: state.pathParameters['id'] as String,
        key: state.pageKey,
      );
    },
    routes: [
      ProfileRouter().editProfile,
      PostRouter().postRoute,
      ChatRouter().chatRoute,
    ],
  );
}

class ChatRouter {
  GoRoute threads = GoRoute(
    path: RouterNames.chatThreadViewRoute,
    builder: (context, state) {
      return ThreadPage(
        key: state.pageKey,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
      PostRouter().postRoute,
    ],
  );
  GoRoute addMembers = GoRoute(
    path: RouterNames.addChatMemberRoute,
    builder: (context, state) {
      return AddMembers(
        key: state.pageKey,
        isAuthorized: state.extra as bool,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
    ],
  );
  GoRoute pending = GoRoute(
    path: RouterNames.pendingChatMembersRoute,
    builder: (context, state) {
      return MemberRequest(
        key: state.pageKey,
        isAuthorized: state.extra as bool,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
    ],
  );
  GoRoute media = GoRoute(
    path: RouterNames.chatMediaViewerRoute,
    builder: (context, state) {
      return ChatMedia(
        key: state.pageKey,
      );
    },
    routes: [
      ChatRouter().chatRoute,
    ],
  );
  GoRoute memberList = GoRoute(
    path: RouterNames.chatMemberListRoute,
    builder: (context, state) {
      return MemberList(
        key: state.pageKey,
      );
    },
    routes: [
      ProfileRouter().profileRoute,
    ],
  );
  GoRoute pinned = GoRoute(
    path: RouterNames.pinnedMessagesRoute,
    builder: (context, state) {
      return PinnedMessages(
        key: state.pageKey,
      );
    },
    routes: [
      ChatRouter().chatRoute,
    ],
  );
  GoRoute videoCall = GoRoute(
    path: RouterNames.videoCallRoute,
    builder: (context, state) {
      final Map data = state.extra as Map;
      return VideoCallView(
        key: state.pageKey,
        host: data['host'],
      );
    },
    routes: [
      ProfileRouter().profileRoute,
    ],
  );
  GoRoute chatRoute = GoRoute(
    path: RouterNames.chatViewRoute,
    builder: (context, state) {
      return MessagesView(
        key: state.pageKey,
      );
    },
    routes: [
      ChatRouter().threads,
      GoRoute(
        path: RouterNames.chatDetailsRoute,
        builder: (context, state) {
          return ChatDetails(key: state.pageKey);
        },
        routes: [
          ChatRouter().addMembers,
          // GoRoute(
          //   path: RouterNames.newChatMemberRoute,
          //   builder: (context, state) {
          //     final Map data = state.extra as Map;
          //     return AddNewMembers(
          //       key: state.pageKey,
          //       isAuthorized: data['isAuthorized'],
          //       channel: data['channel'],
          //     );
          //   },
          // ),
          ChatRouter().pending,
          ChatRouter().media,
          ChatRouter().memberList,
          ChatRouter().pinned,
        ],
      ),
      ChatRouter().videoCall,
    ],
  );
}

class SocialRouter {
  GoRouter router = GoRouter(
    initialLocation: RouterNames.homeRoute,
    navigatorKey: NavKeys()._rootNavigatorKey,
    debugLogDiagnostics: true,
    redirect: (context, state) {
      final isLoggingin = state.matchedLocation == RouterNames.loginRoute;
      final isSignup = state.matchedLocation == RouterNames.signupRoute;
      final loggedIn = supabase.auth.currentUser != null;
      if (!loggedIn && !isLoggingin && !isSignup) return RouterNames.loginRoute;
      if (loggedIn && (isLoggingin || isSignup)) return RouterNames.homeRoute;
      if (loggedIn && state.matchedLocation == '/') {
        return RouterNames.homeRoute;
      }
      return null;
    },
    routes: [
      StatefulShellRoute.indexedStack(
        builder: (context, state, navigationShell) {
          return TabbarViewScaffold(navigationShell);
        },
        branches: [
          // HOME
          StatefulShellBranch(
            navigatorKey: NavKeys()._shellNavigatorHomeKey,
            routes: [
              GoRoute(
                path: RouterNames.homeRoute,
                builder: (context, state) {
                  return Home(
                    key: state.pageKey,
                  );
                },
                routes: [
                  ProfileRouter().profileRoute,
                  GoRoute(
                    path: RouterNames.bookmarkRoute,
                    builder: (context, state) {
                      return Bookmarks(
                        key: state.pageKey,
                      );
                    },
                    routes: [
                      ProfileRouter().profileRoute,
                    ],
                  ),
                  GoRoute(
                    path: RouterNames.settingsRoute,
                    builder: (context, state) {
                      return Settings(
                        key: state.pageKey,
                      );
                    },
                  ),
                  GoRoute(
                    path: RouterNames.privacyRoute,
                    builder: (context, state) {
                      return Privacy(
                        key: state.pageKey,
                      );
                    },
                  ),
                  GoRoute(
                    path: RouterNames.blockListRoute,
                    builder: (context, state) {
                      return BlockedList(
                        key: state.pageKey,
                      );
                    },
                    routes: [
                      ProfileRouter().profileRoute,
                    ],
                  ),
                  GoRoute(
                    path: RouterNames.verificationRoute,
                    builder: (context, state) {
                      return VerificationHome(
                        key: state.pageKey,
                      );
                    },
                  ),
                  GoRoute(
                    path: RouterNames.storyViewRoute,
                    builder: (context, state) {
                      final Map data = state.extra as Map;
                      return StoryViewPage(
                        key: state.pageKey,
                        stories: data['stories'],
                        story: data['story'],
                        updateState: data['updateState'],
                      );
                    },
                    routes: [
                      ProfileRouter().profileRoute,
                    ],
                  ),
                  PostRouter().postRoute,
                ],
              ),
            ],
          ),
          // Chat
          StatefulShellBranch(
            navigatorKey: NavKeys()._shellNavigatorChatKey,
            routes: [
              GoRoute(
                path: RouterNames.chatRoute,
                builder: (context, state) {
                  return Chat(
                    key: state.pageKey,
                  );
                },
                routes: [
                  GoRoute(
                    path: RouterNames.createChatRoute,
                    builder: (context, state) {
                      return CreateChat(
                        key: state.pageKey,
                      );
                    },
                  ),
                  GoRoute(
                    path: RouterNames.chatSearchRoute,
                    builder: (context, state) {
                      return ChatSearchList(
                        key: state.pageKey,
                      );
                    },
                  ),
                  ChatRouter().chatRoute,
                ],
              ),
            ],
          ),
          // Notifications
          StatefulShellBranch(
            navigatorKey: NavKeys()._shellNavigatorNotificationKey,
            routes: [
              GoRoute(
                path: RouterNames.notificationRoute,
                builder: (context, state) {
                  return Notifications(
                    key: state.pageKey,
                  );
                },
              ),
            ],
          ),
          // Livestream
          StatefulShellBranch(
            navigatorKey: NavKeys()._shellNavigatorLivestreamKey,
            routes: [
              GoRoute(
                path: RouterNames.livestreamsRoute,
                builder: (context, state) {
                  return Livestreams(
                    key: state.pageKey,
                  );
                },
                routes: [
                  GoRoute(
                    path: RouterNames.livestreamViewRoute,
                    builder: (context, state) {
                      final Map data = state.extra as Map;
                      return LiveStreamView(
                        key: state.pageKey,
                        isHost: data['isHost'],
                        liveId: state.pathParameters['id'] as String,
                      );
                    },
                  ),
                  GoRoute(
                    path: RouterNames.lobbyViewRoute,
                    builder: (context, state) {
                      final Map data = state.extra as Map;
                      return CallLobby(
                        key: state.pageKey,
                        isLivestream: data['isLivestream'],
                        host: data['host'],
                      );
                    },
                  ),
                ],
              ),
            ],
          ),
          // Search
          StatefulShellBranch(
            navigatorKey: NavKeys()._shellNavigatorSearchKey,
            routes: [
              GoRoute(
                path: RouterNames.searchRoute,
                builder: (context, state) {
                  return SearchPage(
                    key: state.pageKey,
                    query: state.uri.queryParameters['query'],
                  );
                },
                routes: [
                  ProfileRouter().profileRoute,
                  PostRouter().postRoute,
                ],
              ),
            ],
          ),
        ],
      ),
      GoRoute(
        path: RouterNames.createPostRoute,
        parentNavigatorKey: NavKeys()._rootNavigatorKey,
        pageBuilder: (context, state) {
          final Map data = state.extra as Map;
          return MaterialPage(
            fullscreenDialog: true,
            child: CreatePost(
              fromProfile: data['fromProfile'],
              userId: data['userId'],
              username: data['username'],
              shared: data['shared'],
              edit: data['edit'],
              communityId: data['communityId'],
            ),
          );
        },
      ),
      GoRoute(
        path: RouterNames.galleryPickerRoute,
        parentNavigatorKey: NavKeys()._rootNavigatorKey,
        pageBuilder: (context, state) {
          return MaterialPage(
            fullscreenDialog: true,
            child: GalleryPicker(
              fromStory: state.extra as bool,
            ),
          );
        },
        routes: [
          GoRoute(
            path: RouterNames.cameraViewRoute,
            builder: (context, state) {
              return CameraView(
                fromStory: state.extra as bool,
              );
            },
          ),
        ],
      ),
      GoRoute(
        path: RouterNames.loginRoute,
        builder: (context, state) => const Login(),
      ),
      GoRoute(
        path: RouterNames.signupRoute,
        builder: (context, state) => const Signup(),
      ),
      GoRoute(
        path: '/*',
        builder: (context, state) {
          return Scaffold(
            body: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text('404 ${state.fullPath} is not a valid url'),
                OutlinedButton(
                    onPressed: () {
                      context.go('/home');
                    },
                    child: const Text('Go Home'))
              ],
            ),
          );
        },
      )
    ],
  );
}
1

There are 1 best solutions below

3
pcba-dev On BEST ANSWER

GoRouter and AutoRoute use declarative routing (introduced as Flutter Navigator 2.0) which is an approach where developers define the routes available within the application along with their associated destinations in an hierarchical structure where routes are mapped to specific screens (widgets). In contrast with imperative navigation where navigation consists of directly pushing or popping routes onto the navigation stack, developers describe navigation actions which "go" to a destination.

Navigating to a destination using declarative routing will replace the current stack of "screens" with the stack of "screens" configured to be displayed for the destination route. Upon navigation, GoRouter constructs the stack of routes based on the routing configuration and the requested route.

For example, if you define the following routes hierarchy:

/// Root navigator key.
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>();

/// Navigation router.
final GoRouter kRouter = new GoRouter(
  navigatorKey: _rootNavigatorKey,
  initialLocation: PageRoutes.home.path,
  routes: <GoRoute>[
    new GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      name: 'home',
      path: '/',
      builder: (_, routerState) {
        return const HomePage();
      },
    ),
    new GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      name: 'authentication',
      path: '/authentication',
      builder: (_, routerState) {
        return const AuthenticationPage();
      },
    ),
    new GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      name: 'profiles',
      path: '/profiles',
      builder: (_, routerState) {
        return const ProfilesPage();
      },
      routes: [
        GoRoute(
          name: 'profile-details',
          path: ':profileId',
          builder: (context, state) => const ProfileDetailsScreen(id: state.pathParameters['profileId']),
        ),
      ],
    ),
   ],
);

When navigating usiong "go" to /profiles/421654215 it will build the stack of routes bellow: |- ProfilesPage
--->|- ProfileDetailsPage

Which means that if you "pop back" from the ProfileDetailsPage screen it will show the ProfilePage screen, and not the screen that was previously in the view.


If you still want/need to use imperative navigation you can do the following:

  1. Add the following to you main.dart before launching the app:
// Configure GoRouter to reflect imperative APIs in the URL.
GoRouter.optionURLReflectsImperativeAPIs = true;
  1. Define the following navigation function:
/// Pushes a new page onto the navigator stack.
///
/// * For Android it displays a Material entrance and exit animations.
/// * For iOS it displays a iOS-style entrance and exit animations.
/// * For Web or other platforms it displays no animations.
Future<T?> showPage<T extends Object?>({
  required final BuildContext context,
  required final Widget page,
}) {
  late final Route<T> route;
  if (!kIsWeb) {
    if (Platform.isAndroid) {
      route = new MaterialPageRoute(builder: (_) => page);
    } else if (Platform.isIOS) {
      route = new CupertinoPageRoute(builder: (_) => page);
    } else {
      route = NoTransitionPage<T>(
        child: page,
      ).createRoute(context);
    }
  } else {
    route = NoTransitionPage<T>(
      child: page,
    ).createRoute(context);
  }

  return Navigator.of(context, rootNavigator: true).push<T>(route);
}