Flutter: Introducing `url_router` – A simpler Router controller.

When it comes to implementing a url-based Router (aka Nav2) there are really 2 high-level components to the API:

  • A controller to read/write to the current url
  • A parsing/matching system to convert a url into a stack of views, or pages.

The interesting thing here, is that the controller portion, is a fairly stable, boring API. Reading or writing the url location, or accessing queryParams is all pretty straight-forward. Really all you are doing here is setting a string value, and parsing query params using the URI class.

On the other hand, the parsing system has a near infinite number of permutations. As seen in libraries like RouteMaster, GoRouter, VRouter, Beamer, any number of interesting abstractions can be crafted to declaratively parse a url, and render some stack of page objects.

As a developer this can be a little frustrating as it feels like there is no middle ground. In order to gain even basic control of the url location, we are forced to choose a controller that is coupled to some opinionated parsing schema, (or write our own Router from scratch). These parsing schemas themselves are often significantly more complex than the underlying controller API and can obfuscate the basic functionality you’re interested in.

Given this, we thought it would make sense to create a library that handles _only_ the controller portion of a Router, and delegates the location parsing up to the developer.

Introducing url_router 

UrlRouter makes no assumptions about your UI layout. How you react to router.url is up to you. Your UI could be a single page desktop style app, that parses the url inside of it’s main view:

late final router = UrlRouter(
  onGeneratePages: (router) => [
    // Main view will handle this url internally
    MaterialPage(child: MainView(router.url)),
]);

@override
Widget build(BuildContext context) => MaterialApp.router(
  routeInformationParser: UrlRouteParser(),
  routerDelegate: router,
);

Or maybe you build a stack of pages with simple string parsing if your app is quite simple:

late final router = UrlRouter(
  onGeneratePages: (router) {
    return [
      MaterialPage(child: MainView()),
      if(router.url == '/settings')... [
         MaterialPage(child: SettingsView()),
      ]
    ];
  },
);

Controlling Url

UrlRouter offers a small but powerful API to control the url:

APIDescription
.urlread / update the current path
.pushadd a segment to the current path
.popremove a segment from the current path
.onChangingcalled before path is changed, allows for protected paths and redirects
.queryParams read / update the current query parameters

Access the UrlRouter anywhere in your app, using UrlRouter.of(context), or use the context extensions:

  • context.url
  • context.urlPush
  • context.urlPop
  • context.urlRouter

Finally UrlRouter handles redirects, protected routes as well as builder which allows you to wrap UI around the navigator stack:

late final router = UrlRouter(
  onGeneratePages: ...,
  // Optional, protect or redirect urls
  onChanging: (router, newUrl) {
    if (authorized == false) return '/';
    return newUrl;
  },
  // Optional, wrap some outer UI around navigator
  builder: (router, navigator) {
    return Row(
      children: [ 
          const SideBar(), 
          Expanded(child: navigator) 
      ],
    );
  },

);

Small is good

That’s it! That is the entire API. What is so nice about this approach is that the actual implementation of the Router becomes extremely simple, weighing in at only around 120 lines of code for the entire delegate and less than 10 lines for the route information parser. This keeps the bug-surface very small, and the overall api can be quickly grokked.

Who’s it for / What’s the point?

To be clear: if you are looking for a full turn-key “routing system”, you may want to look into one of the other packages mentioned above. There are many options, all with different flavors, and you’re likely to find one that you like.

This package is primarily designed for the developer who would like to roll their own route-matching code, and does not want to deal with the complexity of learning someone else’s system. It provides full control of the url to you, and allows you to layout your app tree accordingly.

This can also just serve as a nice stepping stone for a developer who is currently using onGenerateRoute (or MaterialApp.routes) but they want functional back buttons on the web.

A last set of developers who may be interested are those who are creating their own route-matching libraries, but who do not want to re-invent the controller layer. url_router could easily be wrapped up to serve that purpose.

If you found the package useful, or have any issues, please let us know!

shawn.blais

Shawn has worked as programmer and product designer for over 20 years, shipping several games on Steam and Playstation and dozens of apps on mobile. He has extensive programming experience with Dart, C#, ActionScript, SQL, PHP and Javascript and a deep proficiency with motion graphics and UX design. Shawn is currently the Technical Director for gskinner.

@tree_fortress

3 Comments

  1. I have built a custom system very similar to this package in a current project I am working on. I also used the builder method in order to create outer UI such as a sidebar however found a lot of issues with this method so ended up scrapping it. Some of the issues I found were things such as: overlays / dialogs only opening over the navigator part of the application leaving the sidebar still exposed; dynamically showing / hiding the sidebar on pages like login proved to be a nightmare as the sidebar was completely decoupled from the application; and many more. Have you encountered these issues? if so, how did you solve them and if not, teach m PLEASE!!! XD

  2. Ya this is a common issue and I mention it in the readme 😉
    The trick is to wrap a 2nd navigator around the Sidebar, and use `useRootNavigator=true` when showing modals etc

  3. My apologies, I must admit I had only read this article, not the readme. thanks for the help.

Leave a Reply

Your email address will not be published. Required fields are marked *