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

Issues
Owner
Guilherme Silva
software engineer. Trying to create good code. Not always succeeding :)
Guilherme Silva
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 16 Sep 30, 2021
๐Ÿš— Apple CarPlay for Flutter Apps. Aims to make it safe to use apps made with Flutter in the car by integrating with CarPlay.

CarPlay with Flutter ?? Flutter Apps now on Apple CarPlay! flutter_carplay aims to make it safe to use iPhone apps made with Flutter in the car by int

OฤŸuzhan Atalay 82 Nov 20, 2021
Trying out Flutter for desktop Web app development as an alternative to SPA frameworks (such as React and Angular) by recreating one of the pages of an existing CV Management web app

HTML Renderer Demo CanvasKit Renderer Demo Reddit discussion This repo contains a PoC of using Flutter as a traditional SPA framework for creating a d

Maxim Saplin 11 Nov 8, 2021
Simple UI design implementation of two pages.

dating_app_clone A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started

null 4 Sep 21, 2021
Flutter Multi-platform allows developers to unleash their app to run on the wide variety of different platforms with little or no change.

Flutter Multi-platform sample Flutter Multi-platform allows developers to unleash their app to run on the wide variety of different platforms with lit

MindInventory 19 Sep 23, 2021
A weather app built to learn how to use Canvas and Animation in Flutter.

Weather Quick Disclaimer I removed my private OpenWeather API key from the repo, if you want to get the weather forecast use your own in the openweath

Alessandro Aime 141 Oct 28, 2021
An easy to use side menu in flutter and can used for navigations

Easy Sidemenu Easy sidemenu is An easy to use side menu (bar) for flutter that you can use for navigations in your application. Sidemenu is a menu tha

Mohammad Jamalianpour 16 Oct 7, 2021
A set of real world timelines to showcase the use of timeline_tile package, built with Flutter.

beatiful_timelines Beautiful timelines built with Flutter and timeline_tile. Current examples: Timeline Showcase Football Timeline Activity Timeline S

null 184 Nov 22, 2021
Flutter application to demonstrate use of mPin Animation with custom widget.

Flutter mPin Animation | Custom Widget mPin Widget to accept user input with nice animation. Tutorial ???????????? Flutter mPin Animation Tutorial ||

Afzal Ali 10 Jul 28, 2021
Codeflow 17 Oct 16, 2021
Find The Latest trending and upcoming movies and tv shows with MovieDB app. The app contains all info about movies and tv shows. find similar movies or shows, Browse all genres, video trailers, backdrops, logos, and posters.

MovieDB App Features. Dynamic Theming Search Functionality Onboarding-Screen Select favourite movie Home Screen Tranding movie Movies different catego

Ansh rathod 15 Nov 22, 2021
An app to pick, upload and display images from camera and gallery with size and extension constraints.

image_uploader A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started i

Ehmad Saeedโšก 3 May 24, 2021
A Flutter application with proper navigation and routes handling and API data fetching and posting.

Flutter-Navigation-and-API-Integration A Flutter application with proper navigation and routes handling and API data fetching and posting. โฎ Preview G

Ehmad Saeedโšก 7 Sep 18, 2021
It's a universal app template to have a great animated splash screen and liquid slider. Just change the animation if you want (rive) and change the images or colours according to your app.

liquid A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this i

Zikyan Rasheed 26 Oct 28, 2021
Flutter plugin for selecting images from the Android and iOS image library, taking new pictures with the camera, and edit them before using such as rotation, cropping, adding sticker/text/filters.

advance_image_picker Flutter plugin for selecting multiple images from the Android and iOS image library, taking new pictures with the camera, and edi

Weta Vietnam 64 Nov 24, 2021
Push Notification service for anime episodes and news. The episode updates will be based on actual upload on the internet and NOT Japan tv schedule as other apps do.

Quantz Push Notification service for anime episodes and news. Features Sub and dub - get notified with latest anime episodes on the internet. Ongoing

null 12 Nov 13, 2021
A quick sample app on how to implement a friend list and a profile page in Flutter.

FlutterMates All code resides in the /lib folder: there's no Android / iOS specific code needed. The article, slides and how this came to be, is here.

Codemate Ltd 480 Nov 15, 2021