{"id":15372526,"url":"https://github.com/csells/shell_route_root_back_sample","last_synced_at":"2026-03-18T03:30:18.073Z","repository":{"id":248622160,"uuid":"829213105","full_name":"csells/shell_route_root_back_sample","owner":"csells","description":null,"archived":false,"fork":false,"pushed_at":"2024-07-16T02:57:48.000Z","size":934,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-31T20:16:34.659Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/csells.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-16T01:49:18.000Z","updated_at":"2024-12-22T00:48:29.000Z","dependencies_parsed_at":"2024-07-16T06:06:37.714Z","dependency_job_id":null,"html_url":"https://github.com/csells/shell_route_root_back_sample","commit_stats":null,"previous_names":["csells/shell_route_root_back_sample"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csells%2Fshell_route_root_back_sample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csells%2Fshell_route_root_back_sample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csells%2Fshell_route_root_back_sample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csells%2Fshell_route_root_back_sample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/csells","download_url":"https://codeload.github.com/csells/shell_route_root_back_sample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239929997,"owners_count":19720244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T13:51:55.313Z","updated_at":"2026-03-18T03:30:17.975Z","avatar_url":"https://github.com/csells.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shell_route_root_back_sample\n\nContrary to popular belief, the\n[ShellRoute](https://pub.dev/documentation/go_router/latest/go_router/ShellRoute-class.html)\nclass of [the go_router package](https://pub.dev/packages/go_router) is not just\nfor [enabling the\nBottomNavigationBar](https://medium.com/@ahm4d.bilal/using-gorouters-shellroute-in-flutter-for-nested-navigation-777a9a20642f),\nalthough I admit that it's hard to find a sample that does anything else.\n\nHere's a silly sample I built that builds panes all around the navigated pages\nto illustrate what I mean:\n\n![sample](README/output.gif)\n\nTo enable the back button on the top-level AppBar, I manually added the\nBackButton to the AppBar for the ShellPage, which is the top-level widget\nI defined to hold the top-level UI:\n\n```dart\nclass ShellPage extends StatefulWidget {\n  final Widget child;\n  final ShellPageController controller;\n\n  const ShellPage({super.key, required this.child, required this.controller});\n\n  @override\n  State\u003cShellPage\u003e createState() =\u003e _ShellPageState();\n}\n\nclass _ShellPageState extends State\u003cShellPage\u003e {\n  ...\n\n  @override\n  Widget build(BuildContext context) =\u003e Scaffold(\n        appBar: AppBar(\n          leading: widget.controller.canPop\n              ? BackButton(onPressed: context.pop)\n              : const SizedBox(),\n          title: const Text(App.title),\n        ),\n        body: ... // colored panes and the ShellRoute child widget\n      );\n}\n```\n\nBy default, the AppBar will look at Navigator.canPop and show the BackButton as\nappropriate, but that doesn't work for us, since it looks at the top-level\nNavigator and we want to look at the Navigator created by the ShellRoute. For\nthat to work, we need to reflect the state of the ShellRoute Navigator to the\ntop-level Navigator. To do that, I created a little ShellRouteController:\n\n```dart\nclass ShellPageController extends ChangeNotifier {\n  bool _canPop = false;\n  bool get canPop =\u003e _canPop;\n\n  void navChanged(bool canPop) {\n    _canPop = canPop;\n    notifyListeners();\n  }\n}\n```\n\nThe controller just tracks canPop so that the ShellPage knows when to show the\nBackButton. To know when navigation has changed, I need to hook this bad boy up\nto an instance of the NavigatorObserver:\n\n```dart\nclass ShellNavigatorObserver extends NavigatorObserver {\n  final controller = ShellPageController();\n\n  @override\n  void didPop(Route route, Route? previousRoute) {\n    super.didPop(route, previousRoute);\n    _navChanged(route);\n  }\n\n  @override\n  void didPush(Route route, Route? previousRoute) {\n    super.didPush(route, previousRoute);\n    _navChanged(route);\n  }\n\n  void _navChanged(Route route) =\u003e\n      controller.navChanged(route.navigator?.canPop() ?? false);\n}\n```\n\nThis guy watches for push and pop events from the app's GoRouter instance, then\nuses those to cache the canPop state of the ShellRoute's Navigator.\n\nThen I feed an instance of the ShellNavigatorObserver to the GoRouter, which is\nhow we hook up the whole chain:\n\n```dart\nclass App extends StatelessWidget {\n  static final _rootNavigatorKey = GlobalKey\u003cNavigatorState\u003e();\n  static final _shellNavigatorKey = GlobalKey\u003cNavigatorState\u003e();\n  static final _shellNavigatorObserver = ShellNavigatorObserver();\n\n  final _router = GoRouter(\n    navigatorKey: _rootNavigatorKey,\n    routes: [\n      ShellRoute(\n          navigatorKey: _shellNavigatorKey,\n          builder: (context, state, child) =\u003e ShellPage(\n                controller: _shellNavigatorObserver.controller,\n                child: child,\n              ),\n          observers: [\n            _shellNavigatorObserver\n          ],\n          routes: [...]\n      ),\n    ],\n  );\n\n  ...\n}\n```\n\nNow, when GoRouter does any pushing or popping, i.e. makes a change the back\nstack, we reflect the current canPop state to the controller, which in turn\nnotifies the ShellPage to rebuild, which either shows or hides the BackButton as\nappropriate.\n\nAll of that said, this feels like a hack. I'm sure there's a better way to do\nthis and I'm open to suggestions!\n\n# more context\n[this bug](https://github.com/flutter/flutter/issues/144687)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsells%2Fshell_route_root_back_sample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcsells%2Fshell_route_root_back_sample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsells%2Fshell_route_root_back_sample/lists"}