{"id":13551911,"url":"https://github.com/tomgilder/routemaster","last_synced_at":"2026-02-20T09:31:54.554Z","repository":{"id":37528031,"uuid":"343210648","full_name":"tomgilder/routemaster","owner":"tomgilder","description":"Easy-to-use Navigator 2.0 router for web, mobile and desktop. URL-based routing, simple navigation of tabs and nested routes.","archived":false,"fork":false,"pushed_at":"2026-02-17T09:05:11.000Z","size":1067,"stargazers_count":327,"open_issues_count":84,"forks_count":61,"subscribers_count":12,"default_branch":"main","last_synced_at":"2026-02-17T14:05:55.999Z","etag":null,"topics":["flutter","flutter-package","flutter-router","flutter-routing","navigation"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/routemaster","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomgilder.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-02-28T20:44:36.000Z","updated_at":"2026-02-16T22:30:42.000Z","dependencies_parsed_at":"2024-01-19T06:53:00.308Z","dependency_job_id":"6e1576da-6cc8-4b77-b7bd-0ee55d7c3347","html_url":"https://github.com/tomgilder/routemaster","commit_stats":{"total_commits":294,"total_committers":11,"mean_commits":"26.727272727272727","dds":"0.37755102040816324","last_synced_commit":"decb11a0754ee01e8ea14e3748169c974e7a3baa"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/tomgilder/routemaster","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgilder%2Froutemaster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgilder%2Froutemaster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgilder%2Froutemaster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgilder%2Froutemaster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomgilder","download_url":"https://codeload.github.com/tomgilder/routemaster/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgilder%2Froutemaster/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29647676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-20T09:27:29.698Z","status":"ssl_error","status_checked_at":"2026-02-20T09:26:12.373Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["flutter","flutter-package","flutter-router","flutter-routing","navigation"],"created_at":"2024-08-01T12:01:55.634Z","updated_at":"2026-02-20T09:31:54.548Z","avatar_url":"https://github.com/tomgilder.png","language":"Dart","funding_links":[],"categories":["Dart"],"sub_categories":[],"readme":"# Routemaster \u003cimg src=\"https://openclipart.org/download/286938/Double-Decker-Bus.svg\" width=\"30\"\u003e\n\nHello! Routemaster is an easy-to-use router for Flutter, which wraps over Flutter's Router API.\n\n[![Build](https://github.com/tomgilder/routemaster/actions/workflows/build.yaml/badge.svg)](https://github.com/tomgilder/routemaster/actions/workflows/build.yaml) [![codecov](https://codecov.io/gh/tomgilder/routemaster/branch/main/graph/badge.svg?token=JNF2NV7W09)](https://codecov.io/gh/tomgilder/routemaster)\n[![pub](https://img.shields.io/pub/v/routemaster.svg?color=success)](https://pub.dev/packages/routemaster)\n\n## Features\n\n* Simple declarative mapping from URLs to pages\n* Easy-to-use API: just `Routemaster.of(context).push('/page')`\n* Really easy nested navigation support for tabs\n* Multiple route maps: for example one for a logged in user, another for logged out\n* Observers to easily listen to route changes\n* Covered by over 250 unit, widget and integration tests\n\nHere's the entire routing setup needed for an app featuring tabs and pushed routes:\n\n```dart\nfinal routes = RouteMap(\n  routes: {\n    '/': (_) =\u003e CupertinoTabPage(\n          child: HomePage(),\n          paths: ['/feed', '/settings'],\n        ),\n\n    '/feed': (_) =\u003e MaterialPage(child: FeedPage()),\n    '/settings': (_) =\u003e MaterialPage(child: SettingsPage()),\n    '/feed/profile/:id': (info) =\u003e MaterialPage(\n      child: ProfilePage(id: info.pathParameters['id'])\n    ),\n  }\n);\n\nvoid main() {\n  runApp(\n      MaterialApp.router(\n        routerDelegate: RoutemasterDelegate(routesBuilder: (context) =\u003e routes),\n        routeInformationParser: RoutemasterParser(),\n      ),\n  );\n}\n```\n\nAnd then to navigate:\n\n```dart\nRoutemaster.of(context).push('/feed/profile/1');\n```\n\n...you can see this in action in [this simple app example](https://github.com/tomgilder/routemaster/blob/main/example/simple_example/lib/main.dart).\n\nThere's also a [more advanced example](https://github.com/tomgilder/routemaster/blob/main/example/mobile_app/lib/main.dart).\n\nI would love any feedback you have! Please create an issue for API feedback.\n\n# Documentation\n\nBegin with the quick start below, but also see the [API reference](https://pub.dev/documentation/routemaster/latest/routemaster/routemaster-library.html), [wiki](https://github.com/tomgilder/routemaster/wiki) and [FAQs](https://github.com/tomgilder/routemaster/wiki/FAQs).\n___\n\n\u003cimg src=\"https://openclipart.org/download/286938/Double-Decker-Bus.svg\" width=\"80\"\u003e \n\n# Quick start API tour\n\n* [Overview](#overview)\n* [Routing](#routing)\n* [Tabs](#tabs)\n* [Cupertino tabs](#cupertino-tabs)\n* [Guarded routes](#guarded-routes)\n* [404 Page](#404-page)\n* [Redirect](#redirect)\n* [Swap routing map](#swap-routing-map)\n* [Navigation observers](#navigation-observers)\n* [Navigate without a context](#navigate-without-a-context)\n* [Hero animations](#hero-animations) \n \n## Overview\n\nRoutemaster generates pages based on the current path. This is the key concept of its path-base routing. Path structure matters.\n\nIt uses the path to decide where a page should be pushed. This means the path needs to match your intended page hierarchy.\n\nFor example:\n\n```dart\n'/tabs': (route) =\u003e TabPage(child: HomePage(), paths: ['one', 'two']),\n\n// First tab default page\n'/tabs/one': (route) =\u003e MaterialPage(child: TabOnePage()),\n\n// Second tab default page\n'/tabs/two': (route) =\u003e MaterialPage(child: TabTwoPage()),\n\n// Second tab sub-page: will be displayed in the 2nd tab because it\n// starts with '/tabs/two'\n'/tabs/two/subpage': (route) =\u003e MaterialPage(child: TabTwoPage()),\n\n// Not a tab page: will not be displayed in in a tab\n// because the path doesn't start with '/tabs/one' or '/tabs/two'\n'/tabs/notInATab': (route) =\u003e MaterialPage(child: NotTabPage()),\n```\n\nAny child paths that begin with `/tabs/one` or `/tabs/two` will be pushed into the correct tab. \n\nWhen navigating to `/tabs/two/subpage`, the `TabPage` will be asked \"hey, do you know how to handle this path?\" and it'll go \"sure! it starts with `/tabs/two`, so it goes in my second tab\".\n\nHowever, navigating to `/tabs/notInATab` will **not** be displayed in a tab, but pushed on top of the tab bar.\n\n`TabPage` will be all \"yeah sorry, no idea what to do with that, doesn't match any of my tab paths\" and its parent will be asked to hande it.\n\nPath hierarchy matters, for example [changing where dialogs are displayed](https://github.com/tomgilder/routemaster/wiki/Path-hierarchy).\n  \n## Routing\n\n### Basic app routing setup\n\n```dart\nMaterialApp.router(\n  routerDelegate: RoutemasterDelegate(\n    routesBuilder: (context) =\u003e RouteMap(routes: {\n      '/': (routeData) =\u003e MaterialPage(child: PageOne()),\n      '/two': (routeData) =\u003e MaterialPage(child: PageTwo()),\n    }),\n  ),\n  routeInformationParser: RoutemasterParser(),\n)\n```\n\n###  Navigate from within pages\n\n```dart\nRoutemaster.of(context).push('relative-path');\nRoutemaster.of(context).push('/absolute-path');\n\nRoutemaster.of(context).replace('relative-path');\nRoutemaster.of(context).replace('/absolute-path');\n```\n\n### Path parameters\n\n```dart\n// Path '/products/123' will result in ProductPage(id: '123')\nRouteMap(routes: {\n  '/products/:id': (route) =\u003e MaterialPage(\n        child: ProductPage(id: route.pathParameters['id']),\n      ),\n  '/products/myPage': (route) =\u003e MaterialPage(MyPage()),\n})\n```\n\nNote that routes without path parameters have priority, so in the above example\n`/products/myPage` will show `MyPage`.\n\n### Query parameters\n\n```dart\n// Path '/search?query=hello' results in SearchPage(query: 'hello')\nRouteMap(routes: {\n  '/search': (route) =\u003e MaterialPage(\n        child: SearchPage(query: route.queryParameters['query']),\n      ),\n})\n```\n\n### Get current path info within a widget\n\n```dart\nRouteData.of(context).path; // Full path: '/product/123?query=param'\nRouteData.of(context).pathParameters; // Map: {'id': '123'}\nRouteData.of(context).queryParameters; // Map: {'query': 'param'}\n```\n\n## Tabs\n\nSetup:\n\n```dart\nRouteMap(\n  routes: {\n    '/': (route) =\u003e TabPage(\n          child: HomePage(),\n          paths: ['/feed', '/settings'],\n        ),\n    '/feed': (route) =\u003e MaterialPage(child: FeedPage()),\n    '/settings': (route) =\u003e MaterialPage(child: SettingsPage()),\n  },\n)\n```\n\nMain page:\n\n```dart\nclass HomePage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final tabPage = TabPage.of(context);\n\n    return Scaffold(\n      appBar: AppBar(\n        bottom: TabBar(\n          controller: tabPage.controller,\n          tabs: [\n            Tab(text: 'Feed'),\n            Tab(text: 'Settings'),\n          ],\n        ),\n      ),\n      body: TabBarView(\n        controller: tabPage.controller,\n        children: [\n          for (final stack in tabPage.stacks) PageStackNavigator(stack: stack),\n        ],\n      ),\n    );\n  }\n}\n```\n\n## Cupertino tabs\n\nSetup:\n\n```dart\nRouteMap(\n  routes: {\n    '/': (route) =\u003e CupertinoTabPage(\n          child: HomePage(),\n          paths: ['/feed', '/settings'],\n        ),\n    '/feed': (route) =\u003e MaterialPage(child: FeedPage()),\n    '/settings': (route) =\u003e MaterialPage(child: SettingsPage()),\n  },\n)\n```\n\nMain page:\n\n```dart\nclass HomePage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final tabState = CupertinoTabPage.of(context);\n\n    return CupertinoTabScaffold(\n      controller: tabState.controller,\n      tabBuilder: tabState.tabBuilder,\n      tabBar: CupertinoTabBar(\n        items: [\n          BottomNavigationBarItem(\n            label: 'Feed',\n            icon: Icon(CupertinoIcons.list_bullet),\n          ),\n          BottomNavigationBarItem(\n            label: 'Settings',\n            icon: Icon(CupertinoIcons.search),\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\n## Guarded routes\n\nShow default not found page if validation fails:\n\n```dart\n'/protected-route': (route) =\u003e \n    canUserAccessPage()\n      ? MaterialPage(child: ProtectedPage())\n      : NotFound()\n```\n\nRedirect to another page if validation fails (changes URL):\n\n```dart\n'/protected-route': (route) =\u003e \n    canUserAccessPage()\n      ? MaterialPage(child: ProtectedPage())\n      : Redirect('/no-access'),\n```\n\nShow another page if validation fails (doesn't change URL): \n\n```dart\n'/protected-route': (route) =\u003e \n    canUserAccessPage()\n      ? MaterialPage(child: ProtectedPage())\n      : MaterialPage(child: CustomNoAccessPage())\n```\n\n## 404 Page\n\nDefault page to shown on unknown URL:\n\n```dart\nRouteMap(\n    onUnknownRoute: (route, context) {\n        return MaterialPage(child: NotFoundPage());\n    },\n    routes: {\n        '/': (_) =\u003e MaterialPage(child: HomePage()),\n    },\n)\n```\n\n## Redirect\n\nRedirect one route to another:\n\n```dart\nRouteMap(routes: {\n    '/one': (routeData) =\u003e MaterialPage(child: PageOne()),\n    '/two': (routeData) =\u003e Redirect('/one'),\n})\n```\n\nRedirect all routes to login page, for a logged-out route map:\n\n```dart\nRouteMap(\n  onUnknownRoute: (_) =\u003e Redirect('/'),\n  routes: {\n    '/': (_) =\u003e MaterialPage(child: LoginPage()),\n  },\n)\n```\n\nPassing path parameters from original to the redirect path:\n\n```dart\nRouteMap(routes: {\n    '/user/:id': (routeData) =\u003e MaterialPage(child: UserPage(id: id)),\n    '/profile/:uid': (routeData) =\u003e Redirect('/user/:uid'),\n})\n```\n\n## Swap routing map\n\nYou can swap the entire routing map at runtime.\n\nThis is particularly useful for different pages depending on whether the user is logged in:\n\n```dart\nfinal loggedOutMap = RouteMap(\n  onUnknownRoute: (route, context) =\u003e Redirect('/'),\n  routes: {\n    '/': (_) =\u003e MaterialPage(child: LoginPage()),\n  },\n);\n\nfinal loggedInMap = RouteMap(\n  routes: {\n    // Regular app routes\n  },\n);\n\nMaterialApp.router(\n  routerDelegate: RoutemasterDelegate(\n    routesBuilder: (context) {\n\t\t\t// This will rebuild when AppState changes\n      final appState = Provider.of\u003cAppState\u003e(context);\n      return appState.isLoggedIn ? loggedInMap : loggedOutMap;\n    },\n  ),\n  routeInformationParser: RoutemasterParser(),\n);\n```\n\n## Navigation observers\n\n```dart\nclass MyObserver extends RoutemasterObserver {\n\t// RoutemasterObserver extends NavigatorObserver and\n\t// receives all nested Navigator events\n  @override\n  void didPop(Route route, Route? previousRoute) {\n    print('Popped a route');\n  }\n\n\t// Routemaster-specific observer method\n  @override\n  void didChangeRoute(RouteData routeData, Page page) {\n    print('New route: ${routeData.path}');\n  }\n}\n\nMaterialApp.router(\n  routerDelegate: RoutemasterDelegate(\n    observers: [MyObserver()],\n    routesBuilder: (_) =\u003e routeMap,\n  ),\n  routeInformationParser: RoutemasterParser(),\n);\n```\n\n## Navigate without a context\n\napp.dart\n```dart\nfinal routemaster = RoutemasterDelegate(\n  routesBuilder: (context) =\u003e routeMap,\n);\n\nMaterialApp.router(\n  routerDelegate: routemaster,\n  routeInformationParser: RoutemasterParser(),\n)\n```\n\nmy_widget.dart\n```dart\nimport 'app.dart';\n\nvoid onTap() {\n  routemaster.push('/blah');\n}\n```\n\n## Hero animations\n\nHero animations will work automatically on the top-level navigator (assuming you're using `MaterialApp` or `CupertinoApp`).\n\nFor any child navigators, you'll need to wrap `PageStackNavigator` in a `HeroControllerScope`, like this:\n\n```dart\nHeroControllerScope(\n  controller: MaterialApp.createMaterialHeroController(),\n  child: PageStackNavigator(\n    stack: pageStack,\n  )\n)\n```\n\n# Design goals\n\n* Integrated: work with the Flutter Router API, don't try to replace it. Try to have a very Flutter-like API.\n* Usable: design around user scenarios/stories, such as the ones in [the Flutter storyboard](https://github.com/flutter/uxr/files/5953028/PUBLIC.Flutter.Navigator.API.Scenarios.-.Storyboards.pdf) - [see here for examples](https://github.com/tomgilder/routemaster/wiki/Routemaster-Flutter-scenarios).\n* Opinionated: don't provide 10 options to achieve a goal, but be flexible for all scenarios.\n* Focused: just navigation, nothing else. For example, no dependency injection.\n\nThis project builds on [page_router](https://github.com/johnpryan/page_router).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomgilder%2Froutemaster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomgilder%2Froutemaster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomgilder%2Froutemaster/lists"}