A wrapper around Navigator 2.0 and Router/Pages to make their use a easier.

Overview

APS Navigator - App Pagination System

build codecov style: lint License: MIT pub package

This library is just a wrapper around Navigator 2.0 and Router/Pages API that tries to make their use easier:

🔧 Basic feature set

🚣 What we've tried to achieve:

  • Simple API
  • Easy setup
  • Minimal amount of "new classes types" to learn:
    • No need to extend(or implement) anything
  • Web support (check the images in the following sections):
    • Back/Forward buttons
    • Dynamic URLs
    • Static URLs
    • Recover app state from web history
  • Control of Route Stack:
    • Add/remove Pages at a specific position
    • Add multiples Pages at once
    • Remove a range of pages at once
  • Handles Operational System events
  • Internal(Nested) Navigators

⚠️ What we didn't try to achieve:

  • To use code generation
    • 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
    • The thing is: It doesn't seems natural to me have to use this kind of procedure for something "basic" as navigation
  • To use Strongly-typed arguments passing

👀 Overview

1 - Create the Navigator and define the routes:

final navigator = APSNavigator.from(
  routes: {
    '/dynamic_url_example{?tab}': DynamicURLPage.route,  
    '/': ...
  },
);

2 - Configure MaterialApp to use it:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: navigator,
      routeInformationParser: navigator.parser,
    );
  }
}

3 - Create the widget Page (route):

class DynamicURLPage extends StatefulWidget {
  final int tabIndex;
  const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);

  @override
  _DynamicURLPageState createState() => _DynamicURLPageState();

  // Builder function
  static Page route(RouteData data) {
    final tab = data.values['tab'] == 'books' ? 0 : 1;
    return MaterialPage(
      key: const ValueKey('DynamicURLPage'), // Important! Always include a key
      child: DynamicURLPage(tabIndex: tab),
    );
  }
}
  • You don't need to use a static function as PageBuilder, but it seems to be a good way to organize things.
  • Important: AVOID using 'const' keyword at MaterialPage or DynamicURLPage levels, or Pop may not work correctly with Web History.
  • Important: Always include a Key.

4 - Navigate to it:

 APSNavigator.of(context).push(
    path: '/dynamic_url_example',
    params: {'tab': 'books'},
 );
  • The browser's address bar will display: /dynamic_url_example?tab=books.
  • The Page will be created and put at the top of the Route Stack.

The following sections describe better the above steps.

💆 Usage

1 - Creating the Navigator and defining the Routes:

final navigator = APSNavigator.from(

  // Defines the initial route - default is '/':
  initialRoute: '/dynamic_url_example', 

  //  Defines the initial route params - default is 'const {}':
  initialParams: {'tab': '1'},

  routes: {
    // Defines the location: '/static_url_example'
    '/static_url_example': PageBuilder..,

    // Defines the location (and queries): '/dynamic_url_example?tab=(tab_value)&other=(other_value)'
    // Important: Notice that the '?' is used only once 
    '/dynamic_url_example{?tab,other}': PageBuilder..,

    // Defines the location (and path variables): '/posts' and '/posts/(post_id_value)'
    '/posts': PageBuilder..,
    '/posts/{post_id}': PageBuilder..,

    // Defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)&q2=(q2_value)'.
    '/path/{id}?{?q1,q2}': PageBuilder..,

    // Defines app root - default
    '/': PageBuilder..,
  },
);

routes is just a map between Templates and Page Builders:

  • 📮 Templates are simple strings with predefined markers to Path ({a}) and Query({?a,b,c..}) values.
  • 🏠 Page Builders are plain functions that return a Page and receive a RouteData. Check the section 3 bellow.

Given the configuration above, the app will open at: /dynamic_url_example?tab=1.

2 - Configure MaterialApp:

After creating a Navigator, we need to set it up to be used:

  • 1️⃣ Set it as MaterialApp.router.routeDelegate.
  • 2️⃣ Remember to also add the MaterialApp.router.routeInformationParser:
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: navigator,
      routeInformationParser: navigator.parser,
    );
  }
}

3 - Creating the widget Page(route):

When building a Page:

  • 1️⃣ The library tries to match the address templates with the current address. E.g.:
    • 📮 Template: /dynamic_url_example/{id}{?tab,other}'
    • 🏠 Address: /dynamic_url_example/10?tab=1&other=abc
  • 2️⃣ All paths and queries values are extracted and included in a RouteData.data instance. E.g.:
    • {'id': '10', 'tab': '1', 'other': 'abc'}
  • 3️⃣ This istance is passed as param to the PageBuilder function - static Page route(RouteData data)...
  • 4️⃣ A new Page instance is created and included at the Route Stack - you check that easily using the dev tools.
class DynamicURLPage extends StatefulWidget {
  final int tabIndex;
  const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);

  @override
  _DynamicURLPageState createState() => _DynamicURLPageState();

  // You don't need to use a static function as Builder, 
  // but it seems to be a good way to organize things   
  static Page route(RouteData data) {
    final tab = data.values['tab'] == 'books' ? 0 : 1;
    return MaterialPage(
      key: const ValueKey('DynamicURLPage'), // Important! Always include a key
      child: DynamicURLPage(tabIndex: tab),
    );
  }
}

4 - Navigating to Pages:

Example Link: All Navigating Examples

4.1 - To navigate to a route with query variables:

  • 📮 Template: /dynamic_url_example{?tab,other}
  • 🏠 Address: /dynamic_url_example?tab=books&other=abc
 APSNavigator.of(context).push(
    path: '/dynamic_url_example',
    params: {'tab': 'books', 'other': 'abc'}, // Add query values in [params]
 );

4.2 - To navigate to a route with path variables:

  • 📮 Template: /posts/{post_id}
  • 🏠 Address: /posts/10
 APSNavigator.of(context).push(
    path: '/post/10', // set path values in [path]
 );

4.3 - You can also include params that aren't used as query variables:

  • 📮 Template: /static_url_example
  • 🏠 Address: /static_url_example
 APSNavigator.of(context).push(
    path: '/static_url_example',
    params: {'tab': 'books'}, // It'll be added to [RouteData.values['tab']]
 );

🍷 Details

1. Dynamic URLs Example

Example Link: Dynamic URLs Example

When using dynamic URLs, changing the app's state also changes the browser's URL. To do that:

  • Include queries in the templates. E.g: /dynamic_url_example{?tab}
  • Call updateParams method to update browser's URL:
  final aps = APSNavigator.of(context);
  aps.updateParams(
    params: {'tab': index == 0 ? 'books' : 'authors'},
  );
  • The method above will include a new entry on the browser's history.
  • Later, if the user selects such entry, we can recover the previous widget's State using:
  @override
  void didUpdateWidget(DynamicURLPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    final values = APSNavigator.of(context).currentConfig.values;
    tabIndex = (values['tab'] == 'books') ? 0 : 1;
  }

😪 What is important to know:

  • Current limitation: Any value used at URL must be saved as string.
  • Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.

2. Static URLs Example

Example Link: Static URLs Example

When 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:

  • Don't include queries on route templates. E.g: /static_url_example
  • As we did with Dynamic's URL, call updateParams method again:
  final aps = APSNavigator.of(context);
  aps.updateParams(
    params: {'tab': index == 0 ? 'books' : 'authors'},
  );
  • Then, allow State restoring from browser's history:
  @override
  void didUpdateWidget(DynamicURLPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    final values = APSNavigator.of(context).currentConfig.values;
    tabIndex = (values['tab'] == 'books') ? 0 : 1;
  }

😪 What is important to know:

  • Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.

3. Return Data Example

Example Link: Return Data Example

Push a new route and wait the result:

  final selectedOption = await APSNavigator.of(context).push(
     path: '/return_data_example',
  );

Pop returning the data:

  APSNavigator.of(context).pop('Do!');

😪 What is important to know:

  • Data will only be returned once.
  • 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.
  @override
  void didUpdateWidget(HomePage oldWidget) {
    super.didUpdateWidget(oldWidget);
    final params = APSNavigator.of(context).currentConfig.values;
    result = params['result'] as String;
    if (result != null) _showSnackBar(result!);
  }

4. Multi Push

Example Link: Multi Push Example

Push a list of the Pages at once:

  APSNavigator.of(context).pushAll(
    // position: (default is at top)
    list: [
      ApsPushParam(path: '/multi_push', params: {'number': 1}),
      ApsPushParam(path: '/multi_push', params: {'number': 2}),
      ApsPushParam(path: '/multi_push', params: {'number': 3}),
      ApsPushParam(path: '/multi_push', params: {'number': 4}),
    ],
  );

In the example above ApsPushParam(path: '/multi_push', params: {'number': 4}), will be the new top.

😪 What is important to know:

  • 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.
  • Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.

5. Multi Remove

Example Link: Multi Remove Example

Remove all the Pages you want given a range:

  APSNavigator.of(context).removeRange(start: 2, end: 5);

6. Internal (Nested) Navigators

Example Link: Internal Navigator Example

class InternalNavigator extends StatefulWidget {
  final String initialRoute;

  const InternalNavigator({Key? key, required this.initialRoute})
      : super(key: key);

  @override
  _InternalNavigatorState createState() => _InternalNavigatorState();
}

class _InternalNavigatorState extends State<InternalNavigator> {
  late APSNavigator childNavigator = APSNavigator.from(
    parentNavigator: navigator,
    initialRoute: widget.initialRoute,
    initialParams: {'number': 1},
    routes: {
      '/tab1': Tab1Page.route,
      '/tab2': Tab2Page.route,
    },
  );

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    childNavigator.interceptBackButton(context);
  }

  @override
  Widget build(BuildContext context) {
    return Router(
      routerDelegate: childNavigator,
      backButtonDispatcher: childNavigator.backButtonDispatcher,
    );
  }
}

😪 What is important to know:

  • Current limitation: Browser's URL won't update based on internal navigator state

Warning & Suggestions

  • 🚧 Although this package is already useful, it's still in the Dev stage.
  • 😛 I'm not sure if creating yet another navigating library is something good - we already have a lot of confusion around it today.
  • 💩 This lib is not back-compatible with the old official Navigation API - at least for now (Is it worth it?).
  • 🐛 Do you have any ideas or found a bug? Fell free to open an issue! :)
  • 💁 Do you want to know the current development stage? Check the Project's Roadmap.

Maintainers

You might also like...

Powerful, helpfull, extensible and highly customizable API's that wrap http client to make communication easier with Axelor server with boilerplate code free.

Powerful, helpfull, extensible and highly customizable API's that wrap http client to make communication easier with Axelor server with boilerplate code free.

flutter_axelor_sdk Powerful, helpful, extensible and highly customizable API's that wrap http client to make communication easier with Axelor server w

Dec 25, 2022

Quickly is build as a tool to enhance your Flutter UI development experience and make code easier

Quickly is build as a tool to enhance your Flutter UI development experience and make code easier. It is highly inspired by Bootstrap and Tailwind CSS. It also provide lots of extension methods on String, List and Map.

Oct 24, 2022

An flutter app to make pet adoption easier

An flutter app to make pet adoption easier

An flutter app to make pet adoption easier

Aug 18, 2022

A platform for car sharing where users can book any car that suits their needs and wants for their intended journey, from the closest hosts in the community.

A platform for car sharing where users can book any car that suits their needs and wants for their intended journey, from the closest hosts in the community.

Getting Started This project is a starting point for a Flutter application. For help getting started with Flutter, view our online documentation, whic

Apr 29, 2022

It is a Mobile Application built with Flutter to help Instructors reach their students with the material needed for their course (Videos, PDFs, Exams)

It is a Mobile Application built with Flutter to help Instructors reach their students with the material needed for their course (Videos, PDFs, Exams)

Droos - Flutter Mobile Appliction It is a Mobile Application built with Flutter to help Instructors reach their students with the material needed for

Oct 5, 2022

flutter_thrio makes it easy and fast to add flutter to existing mobile applications, and provide a simple and consistent navigator APIs.

flutter_thrio makes it easy and fast to add flutter to existing mobile applications, and provide a simple and consistent navigator APIs.

中文文档 英文文档 问题集 原仓库不再维护,代码已经很老了 最近版本更新会很快,主要是增加新特性,涉及到混合栈的稳定性的问题应该不多,可放心升级,发现问题加 QQ 群号码:1014085473,我会尽快解决。 不打算好好看看源码的使用者可以放弃这个库了,因为很多设定是比较死的,而我本人不打算花时间写

Dec 29, 2022

flutter_thrio makes it easy and fast to add flutter to existing mobile applications, and provide a simple and consistent navigator APIs.

flutter_thrio makes it easy and fast to add flutter to existing mobile applications, and provide a simple and consistent navigator APIs.

本仓库不再维护,可移步新仓库 https://github.com/flutter-thrio/flutter_thrio 中文文档 问题集 QQ 群号码:1014085473 The Navigator for iOS, Android, Flutter. Version 0.2.2 requir

Dec 5, 2022

A tool to help cli package authors make raising issues like bug reports more interactive for their users.

issue A tool to help cli package authors make raising issues like bug reports more interactive for their users. Features Interactive file based prompt

Oct 18, 2022

flutter_ssf是一个推崇使用Provider、Custom Router、dio结合的MVVM开发模式设计的Flutter应用生产级开发脚手架。

🔥 🔥 flutter_ssf 🔥 🔥 flutter_ssf是一个推崇使用Provider、Custom Router、dio结合的MVVM开发模式设计的Flutter应用生产级开发脚手架。 flutter_ssf只提供基本的参照组件,所以具备几乎所有业务场景中拿来即用的特性。 flutt

Nov 8, 2022
Comments
  • How to use pushAndRemoveUntil() in aps_navigator ?

    How to use pushAndRemoveUntil() in aps_navigator ?

    Thanks for this package.

    Push and pop methods are working as expected. APSNavigator.of(context).push() and APSNavigator.of(context).pop()

    How to use pushAndRemoveUntil and pushNamedAndRemoveUntil methods using aps_navigator ? Some times my requirement is to jump to particular screen instead of previous screen by removing middle routes.

    Please suggest me how to acheive this.

    opened by MaheshPeri19 0
Owner
Guilherme Silva
software engineer. Trying to create good code. Not always succeeding :)
Guilherme Silva
Easy nav - A simple wrapper around flutter navigator, dialogs and snackbar to do those things without context

EasyNav Just a simple wrapper around flutter navigator, dialogs and snackbar to

Abdul Shakoor 2 Feb 26, 2022
App HTTP Client is a wrapper around the HTTP library Dio to make network requests and error handling simpler, more predictable, and less verbose.

App HTTP Client App HTTP Client is a wrapper around the HTTP library Dio to make network requests and error handling simpler, more predictable, and le

Joanna May 44 Nov 1, 2022
A smartphone application called Easy Job goal is to make easier for businesses to find people who meet their standards as well as for job seekers to search for and choose from available positions .

Easy_Jobs 19SW54(MAD-Project) A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to ge

Muskan 2 Nov 6, 2022
Academic master is E-learning app where students can share their doubts wiith their peers they can chat and also they can find their notes

Academic Master is E-learning App. Features:- 1) You can post real Post query in Images and video formates. 2) We will Provide notes,books and previou

amit singh 25 Dec 14, 2022
A wrapper around our Cocoa and Java client library SDKs, providing iOS and Android support for those using Flutter and Dart.

Ably Flutter Plugin A Flutter plugin wrapping the ably-cocoa (iOS) and ably-java (Android) client library SDKs for Ably, the platform that powers sync

Ably Realtime - our client library SDKs and libraries 46 Dec 13, 2022
Breathe is a mental health blogging app where users can join communities of doctors and other users from around the world and both share their problems as well as lend a ear to and help others

?????????????? ?????????????? In a condensed, suffocating society you can feel closed off, when you can't process your emotions and are going through

Soham Sen 3 May 16, 2022
Create a platform for visually challenged individuals to make use of their smart phones

Lights A project aiming to create a platform for visually challenged individuals

Rohit V 0 Mar 22, 2022
A simple dart zeromq implementation/wrapper around the libzmq C++ library

dartzmq A simple dart zeromq implementation/wrapper around the libzmq C++ library Features Currently supported: Creating sockets (pair, pub, sub, req,

Moritz Wirger 18 Dec 29, 2022
Shared preferences typed - A type-safe wrapper around shared preferences, inspired by ts-localstorage

Typed Shared Preferences A type-safe wrapper around shared_preferences, inspired

Philipp Bauer 0 Jan 31, 2022
Addons to supabase dart (and Flutter), to make development easier.

supabase_addons Make great apps with a great backend! Supabase is an open source Firebase alternative. It has support for auth, database and storage u

Bruno D'Luka 20 Dec 3, 2022