{"id":32301277,"url":"https://github.com/guilherme-v/aps_navigator","last_synced_at":"2026-02-21T08:36:28.361Z","repository":{"id":52259879,"uuid":"360369363","full_name":"guilherme-v/aps_navigator","owner":"guilherme-v","description":"A wrapper around Navigator 2.0 and Router/Pages to make their use a easier.","archived":false,"fork":false,"pushed_at":"2021-05-03T02:19:54.000Z","size":17694,"stargazers_count":14,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2025-10-23T05:43:29.782Z","etag":null,"topics":["dart","dynamic-urls","flutter","flutter-package","library","navigation","navigators","router"],"latest_commit_sha":null,"homepage":"","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/guilherme-v.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}},"created_at":"2021-04-22T02:33:26.000Z","updated_at":"2025-09-26T16:49:11.000Z","dependencies_parsed_at":"2022-09-20T22:02:37.550Z","dependency_job_id":null,"html_url":"https://github.com/guilherme-v/aps_navigator","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/guilherme-v/aps_navigator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guilherme-v%2Faps_navigator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guilherme-v%2Faps_navigator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guilherme-v%2Faps_navigator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guilherme-v%2Faps_navigator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guilherme-v","download_url":"https://codeload.github.com/guilherme-v/aps_navigator/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guilherme-v%2Faps_navigator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29677602,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T06:23:40.028Z","status":"ssl_error","status_checked_at":"2026-02-21T06:23:39.222Z","response_time":107,"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":["dart","dynamic-urls","flutter","flutter-package","library","navigation","navigators","router"],"created_at":"2025-10-23T05:38:42.094Z","updated_at":"2026-02-21T08:36:28.356Z","avatar_url":"https://github.com/guilherme-v.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# APS Navigator - App Pagination System\n\n[![build](https://github.com/guilherme-v/aps_navigator/actions/workflows/ci.yaml/badge.svg)](https://github.com/guilherme-v/aps_navigator/actions)\n[![codecov](https://codecov.io/gh/guilherme-v/aps_navigator/branch/develop/graph/badge.svg)](https://codecov.io/gh/guilherme-v/aps_navigator)\n[![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint)\n[![License: MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT)\n[![pub package](https://img.shields.io/pub/v/aps_navigator.svg?color=success)](https://pub.dartlang.org/packages/aps_navigator)\n\u003c!-- [![pub points](https://badges.bar/aps_navigator/pub%20points)](https://pub.dev/packages/aps_navigator) --\u003e\n\nThis library is just a wrapper around Navigator 2.0 and Router/Pages API that tries to make their use easier:\n\n## :wrench: Basic feature set\n\n:rowboat: What we've tried to achieve:\n\n- Simple API\n- Easy setup\n- Minimal amount of \"new classes types\" to learn:\n  - No need to extend(or implement) anything\n- Web support (check the images in the following sections):\n  - Back/Forward buttons\n  - Dynamic URLs\n  - Static URLs\n  - Recover app state from web history\n- Control of Route Stack:\n  - Add/remove Pages at a specific position\n  - Add multiples Pages at once\n  - Remove a range of pages at once\n- Handles Operational System events\n- Internal(Nested) Navigators\n\n:warning: What we didn't try to achieve:\n\n- To use code generation\n  - Don't get me wrong. Code generation is a fantastic technique that makes code clear and coding faster - we have great libraries that are reference in the community and use it\n  - The thing is: It doesn't seems natural to me have to use this kind of procedure for something \"basic\" as navigation\n- To use Strongly-typed arguments passing\n\n## :eyes: Overview\n\n### 1 - Create the Navigator and define the routes:\n\n```dart\nfinal navigator = APSNavigator.from(\n  routes: {\n    '/dynamic_url_example{?tab}': DynamicURLPage.route,  \n    '/': ...\n  },\n);\n```\n\n### 2 - Configure MaterialApp to use it:\n\n```dart\nclass MyApp extends StatelessWidget {\n  const MyApp({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerDelegate: navigator,\n      routeInformationParser: navigator.parser,\n    );\n  }\n}\n```\n\n### 3 - Create the widget Page (route):\n\n```dart\nclass DynamicURLPage extends StatefulWidget {\n  final int tabIndex;\n  const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);\n\n  @override\n  _DynamicURLPageState createState() =\u003e _DynamicURLPageState();\n\n  // Builder function\n  static Page route(RouteData data) {\n    final tab = data.values['tab'] == 'books' ? 0 : 1;\n    return MaterialPage(\n      key: const ValueKey('DynamicURLPage'), // Important! Always include a key\n      child: DynamicURLPage(tabIndex: tab),\n    );\n  }\n}\n```\n\n- You don't need to use a static function as PageBuilder, but it seems to be a good way to organize things.\n- Important: **AVOID** using '**const**' keyword at `MaterialPage` or `DynamicURLPage` levels, or Pop may not work correctly with Web History.\n- Important: **Always** include a Key.\n\n### 4 - Navigate to it:\n\n```dart\n APSNavigator.of(context).push(\n    path: '/dynamic_url_example',\n    params: {'tab': 'books'},\n );\n```\n\n- The browser's address bar will display: `/dynamic_url_example?tab=books`.\n- The `Page` will be created and put at the top of the Route Stack.\n\nThe following sections describe better the above steps.\n\n## :massage: Usage\n\n### 1 - Creating the Navigator and defining the Routes:\n\n```dart\nfinal navigator = APSNavigator.from(\n\n  // Defines the initial route - default is '/':\n  initialRoute: '/dynamic_url_example', \n\n  //  Defines the initial route params - default is 'const {}':\n  initialParams: {'tab': '1'},\n\n  routes: {\n    // Defines the location: '/static_url_example'\n    '/static_url_example': PageBuilder..,\n\n    // Defines the location (and queries): '/dynamic_url_example?tab=(tab_value)\u0026other=(other_value)'\n    // Important: Notice that the '?' is used only once \n    '/dynamic_url_example{?tab,other}': PageBuilder..,\n\n    // Defines the location (and path variables): '/posts' and '/posts/(post_id_value)'\n    '/posts': PageBuilder..,\n    '/posts/{post_id}': PageBuilder..,\n\n    // Defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)\u0026q2=(q2_value)'.\n    '/path/{id}?{?q1,q2}': PageBuilder..,\n\n    // Defines app root - default\n    '/': PageBuilder..,\n  },\n);\n```\n\n`routes` is just a map between `Templates` and `Page Builders`:\n\n- :postbox: `Templates` are simple strings with predefined markers to Path (`{a}`) and Query(`{?a,b,c..}`) values.\n- :house: `Page Builders` are plain functions that return a `Page` and receive a `RouteData`. Check the section 3 bellow.\n\nGiven the configuration above, the app will open at: `/dynamic_url_example?tab=1`.\n\n### 2 -  Configure MaterialApp:\n\nAfter creating a Navigator, we need to set it up to be used:\n\n- :one: Set it as `MaterialApp.router.routeDelegate`.\n- :two: Remember to also add the `MaterialApp.router.routeInformationParser`:\n\n```dart\nclass MyApp extends StatelessWidget {\n  const MyApp({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerDelegate: navigator,\n      routeInformationParser: navigator.parser,\n    );\n  }\n}\n```\n\n### 3 - Creating the widget Page(route):\n\nWhen building a `Page`:\n\n- :one: The library tries to match the address `templates` with the current address. E.g.:\n  - :postbox: Template: `/dynamic_url_example/{id}{?tab,other}'`\n  - :house: Address: `/dynamic_url_example/10?tab=1\u0026other=abc`\n- :two: All *paths* and *queries* values are extracted and included in a `RouteData.data` instance. E.g.:\n  - `{'id': '10', 'tab': '1', 'other': 'abc'}`\n- :three: This istance is passed as param to the `PageBuilder` function - `static Page route(RouteData data)`...\n- :four: A new Page instance is created and included at the Route Stack - you check that easily using the dev tools.\n\n```dart\nclass DynamicURLPage extends StatefulWidget {\n  final int tabIndex;\n  const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);\n\n  @override\n  _DynamicURLPageState createState() =\u003e _DynamicURLPageState();\n\n  // You don't need to use a static function as Builder, \n  // but it seems to be a good way to organize things   \n  static Page route(RouteData data) {\n    final tab = data.values['tab'] == 'books' ? 0 : 1;\n    return MaterialPage(\n      key: const ValueKey('DynamicURLPage'), // Important! Always include a key\n      child: DynamicURLPage(tabIndex: tab),\n    );\n  }\n}\n```\n\n### 4 - Navigating to Pages:\n\nExample Link: [All Navigating Examples](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/home_page.dart)\n\n4.1 - To navigate to a route with **query variables**:\n\n- :postbox: Template: `/dynamic_url_example{?tab,other}`\n- :house: Address:  `/dynamic_url_example?tab=books\u0026other=abc`\n\n```dart\n APSNavigator.of(context).push(\n    path: '/dynamic_url_example',\n    params: {'tab': 'books', 'other': 'abc'}, // Add query values in [params]\n );\n```\n\n4.2 - To navigate to a route with **path variables**:\n\n- :postbox: Template: `/posts/{post_id}`\n- :house: Address:  `/posts/10`\n\n```dart\n APSNavigator.of(context).push(\n    path: '/post/10', // set path values in [path]\n );\n```\n\n4.3 - You can also include params that **aren't** used as query variables:\n\n- :postbox: Template: `/static_url_example`\n- :house: Address:  `/static_url_example`\n\n```dart\n APSNavigator.of(context).push(\n    path: '/static_url_example',\n    params: {'tab': 'books'}, // It'll be added to [RouteData.values['tab']]\n );\n```\n\n## :wine_glass: Details\n\n### 1. Dynamic URLs Example\n\nExample Link: [Dynamic URLs Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/dynamic_url_page.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/dynamic_url_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\nWhen using dynamic URLs, changing the app's state also changes the browser's URL. To do that:\n\n- Include queries in the templates. E.g: `/dynamic_url_example{?tab}`\n- Call `updateParams` method to update browser's URL:\n\n```dart\n  final aps = APSNavigator.of(context);\n  aps.updateParams(\n    params: {'tab': index == 0 ? 'books' : 'authors'},\n  );\n```\n\n- The method above will include a new entry on the browser's history.\n- Later, if the user selects such entry, we can recover the previous widget's `State` using:\n\n```dart\n  @override\n  void didUpdateWidget(DynamicURLPage oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final values = APSNavigator.of(context).currentConfig.values;\n    tabIndex = (values['tab'] == 'books') ? 0 : 1;\n  }\n```\n\n:sleepy: What is important to know:\n\n- Current limitation: Any value used at URL must be saved as `string`.\n- Don't forget to include a `Key` on the `Page` created by the `PageBuilder` to everything works properly.\n\n### 2. Static URLs Example\n\nExample Link: [Static URLs Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/static_url_page.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/static_url_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\nWhen using static URLs, changing the app's state doesn't change the browser's URL, but it'll generate a new entry on the history. To do that:\n\n- Don't include queries on route templates. E.g: `/static_url_example`\n- As we did with Dynamic's URL, call `updateParams` method again:\n\n```dart\n  final aps = APSNavigator.of(context);\n  aps.updateParams(\n    params: {'tab': index == 0 ? 'books' : 'authors'},\n  );\n```\n\n- Then, allow `State` restoring from browser's history:\n\n```dart\n  @override\n  void didUpdateWidget(DynamicURLPage oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final values = APSNavigator.of(context).currentConfig.values;\n    tabIndex = (values['tab'] == 'books') ? 0 : 1;\n  }\n```\n\n:sleepy: What is important to know:\n\n- Don't forget to include a `Key` on the `Page` created by the `PageBuilder` to everything works properly.\n\n### 3. Return Data Example\n\nExample Link: [Return Data Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/return_data_page.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/return_data_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\nPush a new route and wait the result:\n\n```dart\n  final selectedOption = await APSNavigator.of(context).push(\n     path: '/return_data_example',\n  );\n```\n\nPop returning the data:\n\n```dart\n  APSNavigator.of(context).pop('Do!');\n```\n\n:sleepy: What is important to know:\n\n- Data will only be returned once.\n- In case of user navigate your app and back again using the browser's history, the result will be returned at `didUpdateWidget` method as `result,` instead of `await` call.\n\n```dart\n  @override\n  void didUpdateWidget(HomePage oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final params = APSNavigator.of(context).currentConfig.values;\n    result = params['result'] as String;\n    if (result != null) _showSnackBar(result!);\n  }\n```\n\n### 4. Multi Push \n\nExample Link: [Multi Push Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/multi/multi_push_page.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/multi_push_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\nPush a list of the Pages at once:\n\n```dart\n  APSNavigator.of(context).pushAll(\n    // position: (default is at top)\n    list: [\n      ApsPushParam(path: '/multi_push', params: {'number': 1}),\n      ApsPushParam(path: '/multi_push', params: {'number': 2}),\n      ApsPushParam(path: '/multi_push', params: {'number': 3}),\n      ApsPushParam(path: '/multi_push', params: {'number': 4}),\n    ],\n  );\n```\n\nIn the example above `ApsPushParam(path: '/multi_push', params: {'number': 4}),` will be the new top.\n\n:sleepy: What is important to know:\n\n- You don't necessarily have to add at the top; you can use the `position` param to add the routes at the middle of Route Stack.\n- Don't forget to include a `Key` on the `Page` created by the `PageBuilder` to everything works properly.\n\n### 5. Multi Remove\n\nExample Link: [Multi Remove Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/multi/multi_remove_page.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/multi_remove_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\nRemove all the Pages you want given a range:\n\n```dart\n  APSNavigator.of(context).removeRange(start: 2, end: 5);\n```\n\n### 6. Internal (Nested) Navigators\n\nExample Link: [Internal Navigator Example](https://github.com/guilherme-v/aps_navigator/blob/develop/example/lib/pages/examples/internal_navigator/internal_navigator.dart)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/guilherme-v/aps_navigator/develop/gif/internal_nav_example.gif\" height=\"340\"\u003e\n\u003c/p\u003e\n\n\n```dart\nclass InternalNavigator extends StatefulWidget {\n  final String initialRoute;\n\n  const InternalNavigator({Key? key, required this.initialRoute})\n      : super(key: key);\n\n  @override\n  _InternalNavigatorState createState() =\u003e _InternalNavigatorState();\n}\n\nclass _InternalNavigatorState extends State\u003cInternalNavigator\u003e {\n  late APSNavigator childNavigator = APSNavigator.from(\n    parentNavigator: navigator,\n    initialRoute: widget.initialRoute,\n    initialParams: {'number': 1},\n    routes: {\n      '/tab1': Tab1Page.route,\n      '/tab2': Tab2Page.route,\n    },\n  );\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    childNavigator.interceptBackButton(context);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Router(\n      routerDelegate: childNavigator,\n      backButtonDispatcher: childNavigator.backButtonDispatcher,\n    );\n  }\n}\n```\n\n:sleepy: What is important to know:\n\n- Current limitation: Browser's URL won't update based on internal navigator state\n\n## Warning \u0026 Suggestions\n\n- :construction: Although this package is already useful, it's still in the **Dev stage**.\n- :stuck_out_tongue: I'm not sure if creating yet another navigating library is something good - we already have a lot of confusion around it today.\n- :hankey: This lib is not back-compatible with the old official Navigation API - at least for now (Is it worth it?).\n- :bug: Do you have any ideas or found a bug? Fell free to open an issue! :)\n- :information_desk_person: Do you want to know the current development stage? Check the [Project's Roadmap](https://github.com/guilherme-v/aps_navigator/projects/1).\n\n## Maintainers\n\n- [Gui Silva](https://github.com/guilherme-v)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguilherme-v%2Faps_navigator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguilherme-v%2Faps_navigator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguilherme-v%2Faps_navigator/lists"}