Navi - A declarative navigation framework for Flutter, based on Navigator 2.0.

Note that, imperative navigation API is also supported as an extra layer beyond the declarative API at lower layer.

pub package

Quick Setup

To use the library, RouteStack widget is everything you need to learn!

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  final _informationParser = NaviInformationParser();
  final _routerDelegate = NaviRouterDelegate.material(rootPage: RootPage());

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _informationParser,
      routerDelegate: _routerDelegate,
    );
  }
}
class YourStackState {}

class RootPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // you can nest RouteStack under another RouteStack without limitation to create nested routes
    return RouteStack<YourStackState>(
      // mandatory properties
      pages: (context, state) => [], // which pages to show for current state
      updateStateOnNewRoute: (routeInfo) => YourStackState(), // If user enter a URL manually, what is the initial state for the given URL

      // optional properties
      updateRouteOnNewState: (state) => const RouteInfo(), // what is the URL for current state
      updateStateBeforePop: (context, route, dynamic result, state) => YourStackState(), // what is the state when user click in-app back button / pop()
      marker: const StackMarker<YourStackState>(), // if you need to access your stack in a deeper child widget
      controller: StackController<YourStackState>(), // if you need to control this stack in RootPage widget
    );
  }
}

Examples

Architecture layers

PackagesLayersPlanExplanation
NaviCode GeneratorAfter release 1.0Generate boilerplate code
NaviConfiguratorBefore release 1.0 if possible, otherwise after release 1.0Comparable to URL mapping approaches like Angular or Vue
NaviImperative APIBefore release 1.0Useful when declarative is not needed
NaviHigh-level declarative APIWIPSimple and easy to use yet keep the powerful of Navigator 2.0
Flutter SDK Navigator 2.0Low-level declarative APIN/AToo complex and difficult to use

Declarative navigation

If you love Flutter, you would love declarative UI.

Declarative navigation is similar as it allows you to describe your navigation system by the current UI state. Updating your current UI state to tell Navi figures out where to navigate to.

Using declarative navigation is only powerful if

  • the provided API is simple enough
  • your application is reasonable split into manageable domains (or stacks in this library)

An example, where declarative shines is to manage a chain of pages to complete a single task (ex. registration form with multiple pages). Using imperative approach is usually more difficult in this case.

Chain of pages scenario (also known as flow of pages) is just one case, you can use with Navi. This library is definitely much more than that.

Introduction and Milestones

The goal of Navi package is to create a friendly declarative navigation API for Flutter projects. It depends heavily on Navigator 2.0.

  • Milestone 1 (WIP)
    • Easy to learn, simple to maintain and organize application code based on split domains.
    • Keep boilerplate code at reasonable level. More optimization will be in next milestones.
    • Flexible: easily integrate with other architectural elements, especially, state management (ex. Bloc) and dependency injection (ex. get_it.
    • Modularization
      • friendly to projects, which require splitting into multiple teams
      • each stack can be considered as an isolated module
      • stacks should be reusable in other stacks
      • developers can freely organize stacks in the way they want
    • Imperative navigation API is also supported.
  • Milestone 2 (Plan: before release 1.0)
    • Optimize to remove boilerplate code for common/general scenarios
    • Optimize performance
    • Test coverage at least 90%
    • Evaluate edge cases
  • Milestone 3 (Plan: before release 1.0 if possible, otherwise after release 1.0)
    • Implement a configurator, which fits to common scenarios. For more control, please use the high level declarative API.
  • Milestone 4 (Plan: after release 1.0)
    • Implement code generator to even remove more boilerplate code

Please see this full source code example app.

Nested stack

Because RouteStack is just a normal widget, you only need to use this widget to build nested stacks like you would do with other widgets.

It's commonly used together with BottomNavigationBar, but it will definitely work with other components and designs.

If you want to keep state of nested stacks, you could use IndexedStack.

For example, you have a bookstore with 2 pages: book list page and book page. Their URLs are /books and /books/:id.

In book page, you split the content into 3 tabs: overview, details and reviews. Their URLs are /books/:id/overview , /books/:id/details, /books/:id/reviews.

In this case, you can create 2 stacks:

// This stack could be your root stack, maybe directly under your MaterialApp.
RouteStack(
  pages: (context, state) => [BookListPage(), BookPage()],
  updateRouteOnNewState: (state) {
    // map /books to BookListPage()
    // map /books/:id to BookPage()
  },
);


// This stack is a child widget of BookPage widget
RouteStack(
  pages: (context, state) => [BookOverviewPage(), BookDetailsPage(), BookReviewsPage()],
  updateRouteOnNewState: (state) {
    // map /overview to BookOverviewPage()
    // map /details to BookDetailsPage()
    // map /reviews to BookReviewsPage()
  },
)

The main idea is that, in the nested stack, you don't need to know the URL of parent stack.

Navi will help you merge the current URL in parent stack (ex. /books/1) and nested stack (ex. /overview) to generate the final URL for you (ex. /books/1/overview).

You can have unlimited nested stacks as deep as you want and each stack manage only the URL part it should know.

Please see more in this example.

TODO: Flatten list of stacks to a single stack

FlatRouteStack(
  children: [
    RouteStack(),
    RouteStack(),
    // ...
  ]
)

FlatRouteStack merges all pages of child stacks into a single stack.

The difference is that, URL of nested stacks are dependent, but URLs of stacks in FlatRouteStack are independent.

How to navigate?

  • Implemented

    • context.navi.stack(ProductStackMarker()).state = 1: navigate to product stack with productId = 1.
    • context.navi.byUrl('/products/1'): navigate to absolute URL (begin with a slash)
  • TODOs:

    • context.navi.byUrl('details'): navigate to relative URL
    • context.navi.pop(): a shortcut of Navigator.of(context).pop()
    • context.navi.stack(ProductStackMarker()).state.pop(): move up one level at the specified stack or exit if there's no upper page.
    • context.navi.back(): move back to the previous page in the history.

Sync URL and stack state

You will have 2 options to choose:

  • Don't use code generator: path parameters and query parameters are provided to you as String. You need to manually validate and convert to your types.
  • TODO: Use code generator to generate typesafe interfaces, which allow you to sync path parameters and query parameters to your variable in the defined types automatically.

TODO: Manipulation of the chronological history Stack

Browser back button and system back button should behave the same way, according to material navigation guideline

Contributing to Navi

First of all, thank you a lot to visit Navi project!

  • Everyone is welcome
    • to file issues on GitHub
    • to help people asking for help
    • click the Github star/watch button
    • click the Pub.dev like button
    • to contribute code via pull requests

The more people interested in the project, the more motivation I will have to speed up the development.

Enjoy to use Navi!

Libraries