pub test style

GitHub commit activity GitHub open issues GitHub closed issues Licence

Buy Me A Coffee

Handle your application routing, synchronize it with browser URL and more. Beamer uses the power of Navigator 2.0 API and implements all the underlying logic for you.


Key Concepts

The key concept of Beamer is a BeamLocation which represents a stack of one or more pages. You will be extending BeamLocation to define your app's locations to which you can then beam to using

Beamer.of(context).beamTo(MyLocation())
// or context.beamTo(MyLocation())

or beamTo a specific configuration of some location;

context.beamTo(
  BooksLocation(
    pathBlueprint: '/books/:bookId',
    pathParameters: {'bookId': '2'},
  ),
),

You can think of it as teleporting / beaming to another place in your app. Similar to Navigator.of(context).pushReplacementNamed('/my-route'), but Beamer is not limited to a single page, nor to a push per se. You can create an arbitrary stack of pages that gets build when you beam there.

Using Beamer can feel like using many of Navigator's push/pop methods at once.


Note that "Navigator 1.0" can be used alongside Beamer. You can easily push or pop pages with Navigator.of(context), but those will not be contributing to the URI. This is often needed when some info/helper page needs to be shown that doesn't influence the browser's URL. And of course, when using Beamer on mobile, this is a non-issue as there is no URL.

Examples

Books

Here is a recreation of books example from this article where you can learn a lot about Navigator 2.0. See Example for full application code of this example.

example-books

Advanced Books

For a step further, we add more flows to demonstrate the power of Beamer. The full code is available here.

Deep Location

You can instantly beam to a location in your app that has many pages stacked (deep linking) and then pop them one by one or simply beamBack to where you came from. The full code is available here. Note that beamBackOnPop parameter of beamTo might be useful here to override AppBar's pop with beamBack.

ElevatedButton(
  onPressed: () => context.beamTo(DeepLocation('/a/b/c/d')),
  // onPressed: () => context.beamTo(DeepLocation('/a/b/c/d'), beamBackOnPop: true),
  child: Text('Beam deep'),
),

example-deep-location

Location Builder

You can override BeamLocation.builder to provide some data to the entire location, i.e. to all of the pages. The full code is available here.

@override
Widget builder(BuildContext context, Navigator navigator) {
  return MyProvider<MyObject>(
    create: (context) => MyObject(),
    child: navigator,
  );
}

Guards

You can define global guards (for example, authentication guard) or location guards that keep a specific location safe. The full code is available here.

  • Global Guards
BeamerRouterDelegate(
  initialLocation: initialLocation,
  guards: [
    BeamGuard(
      pathBlueprints: ['/books*'],
      check: (context, location) => AuthenticationStateProvider.of(context).isAuthenticated.value,
      beamTo: (context) => LoginLocation(),
    ),
  ],
),
  • Location (local) Guards
// in your location implementation
@override
List<BeamGuard> get guards => [
  BeamGuard(
    pathBlueprints: ['/books/*'],
    check: (context, location) => location.pathParameters['bookId'] != '2',
    showPage: forbiddenPage,
  ),
];

example-guards

Inner Beamer

An example of putting Beamer into the widget tree. This is not yet fully functional for web usage; setting the URL from browser doesn't update the state properly. It should work when nested routers issue is settled. The full code is available here.

Scaffold(
  body: Beamer(
    routerDelegate:
        BeamerRouterDelegate(initialLocation: _beamLocations[0]),
    routeInformationParser: BeamerRouteInformationParser(
      beamLocations: _beamLocations,
    ),
  ),
),


Integration with Navigation UI Packages

example-bottom-navigation

Usage

On Entire App

In order to use Beamer on your entire app, you must (as per official documentation) construct your *App widget with .router constructor to which (along with all your regular *App attributes) you provide

  • routerDelegate that controls (re)building of Navigator pages and
  • routeInformationParser that decides which URI corresponds to which Router state/configuration, in our case - BeamLocation.

Here you use the Beamer implementation of those - BeamerRouterDelegate and BeamerRouteInformationParser, to which you pass your BeamLocations.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: BeamerRouterDelegate(
        initialLocation: HomeLocation(),
      ),
      routeInformationParser: BeamerRouteInformationParser(
        beamLocations: [
          HomeLocation(),
          BooksLocation(),
        ],
      ),
      ...
    );
  }
}

As a Widget

class MyApp extends StatelessWidget {
  final _beamerKey = GlobalKey<BeamerState>();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Beamer(
          key: _beamerKey,
          routerDelegate:
              BeamerRouterDelegate(initialLocation: _beamLocations[0]),
          routeInformationParser: BeamerRouteInformationParser(
            beamLocations: _beamLocations,
          ),
        ),
        bottomNavigationBar: BottomNavigationBarWidget(
          beamerKey: _beamerKey,
        ),
      ),
    );
  }
}

General Notes

  • When extending BeamLocation, two getters need to be implemented; pathBlueprints and pages.

    • pages represent a stack that will be built by Navigator when you beam there, and pathBlueprints is there for Beamer to decide which BeamLocation corresponds to an URL coming from browser.
    • BeamLocation takes query and path parameters from URI. The : is necessary in pathBlueprints if you might get path parameter from browser.
  • BeamPage's child is an arbitrary Widgets that represent your app screen / page.

    • key is important for Navigator to optimize rebuilds. This should be an unique value for "page state".
    • BeamPage creates MaterialPageRoute, but you can extends BeamPage and override createRoute to make your own implementation instead.

Migrating from 0.4.x to >=0.5.x

  • instead of wrapping MaterialApp with Beamer, use *App.router()
  • String BeamLocation.pathBlueprint is now List<String> BeamLocation.pathBlueprints
  • BeamLocation.withParameters constructor is removed and all parameters are handled with 1 constructor. See example if you need super.
  • BeamPage.page is now called BeamPage.child

Contributing

This package is still in early stages. To see the upcoming features, check the Issue board.

If you notice any bugs not present in issues, please file a new issue. If you are willing to fix or enhance things yourself, you are very welcome to make a pull request. Before making a pull request;

  • if you wish to solve an existing issue, please let us know in issue comments first
  • if you have another enhancement in mind, create an issue for it first so we can discuss your idea

Also, you can Buy Me A Coffee to speed up the development.

Libraries

beamer