beamer 1.0.0-nullsafety.6 beamer: ^1.0.0-nullsafety.6 copied to clipboard
A router that lets you navigate through guarded page stacks and URLs using the Navigator 2.0 API effortlessly.
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 whose ordering and rebuild you control within pagesBuilder
.
Beaming #
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 with an extension on BuildContext
context.beamTo(MyLocation());
or beam to a specific configuration of some location;
context.beamToNamed('/books/2');
// or more explicitly
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 define an arbitrary stack of pages that get built when you beam there. Using Beamer can feel like using many of Navigator
's push/pop
methods at once.
You can carry an arbitrary key/value data
in a location. This can be accessed from every page and updated as you travel through a location.
context.beamToNamed(
'/book/2',
data: {'note': 'this is my favorite book'},
);
Updating #
Once at a BeamLocation
, it is preferable to update the current location's state. For example, for going from /books
to /books/3
(which are both handled by BooksLocation
);
context.currentBeamLocation.update(
(state) => state.copyWith(
pathBlueprintSegments: ['books', ':bookId'],
pathParameters: {'bookId': '3'},
),
),
Note that both beaming functions (beamTo
and BeamToNamed
) will have the same effect as update
when you try to beam to a location which you're currently on, e.g. if you would to call context.beamToNamed('/books/3')
instead of above code.
Beaming Back #
All BeamLocation
s that you visited are kept in beamHistory
. Therefore, there is an ability to beam back to the previous BeamLocation
. For example, after spending some time on /books
and /books/3
, say you beam to /articles
which is handled by another BeamLocation
(e.g. ArticlesLocation
). From there, you can get back to your previous location as it were when you left, i.e. /books/3
;
context.beamBack();
Note that Beamer will remove duplicate locations from beamHistory
as you go. For example, if you visit BooksLocation
, ArticlesLocation
and then BooksLocation
again, the first instance of BooksLocation
will be removed from history and beamHistory
will be [ArticlesLocation,BooksLocation]
instead of [BooksLocation,ArticlesLocation,BooksLocation]
. You can turn that off by setting BeamerRouterDelegate.removeDuplicateHistory
to false
.
You can check whether you can beam back with context.canBeamBack
or even inspect the location you'll be beaming back to: context.beamBackLocation
.
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.
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'),
),
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.
// in your location implementation
@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(
beamLocations: [
HomeLocation(),
LoginLocation(),
BooksLocation(),
],
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,
),
];
Beamer Widget #
Examples of putting Beamer
s into the Widget tree, when you need nested navigation.
class MyApp extends StatelessWidget {
final _beamerKey = GlobalKey<BeamerState>();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: BeamerRouteInformationParser(),
routerDelegate: RootRouterDelegate(
homeBuilder: (context, uri) => Scaffold(
body: Beamer(
key: _beamerKey,
beamLocations: _beamLocations,
),
bottomNavigationBar: BottomNavigationBarWidget(
beamerKey: _beamerKey,
),
),
),
);
}
}
class MyAppState extends State<MyApp> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
Beamer(
beamLocations: [ArticlesLocation()],
),
Container(
color: Colors.blueAccent,
padding: const EdgeInsets.all(32.0),
child: Beamer(
beamLocations: [BooksLocation()],
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(label: 'A', icon: Icon(Icons.article)),
BottomNavigationBarItem(label: 'B', icon: Icon(Icons.book)),
],
onTap: (index) => setState(() => _currentIndex = index),
),
),
);
}
}
...
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: BeamerRouteInformationParser(),
routerDelegate: RootRouterDelegate(
beamLocations: [
HomeLocation(),
],
),
);
}
}
...
class HomeLocation extends BeamLocation {
@override
List<String> get pathBlueprints => ['/*'];
@override
List<BeamPage> pagesBuilder(BuildContext context) => [
BeamPage(
key: ValueKey('home'),
child: HomeScreen(),
)
];
}
...
class HomeScreen extends StatelessWidget {
final _beamerKey = GlobalKey<BeamerState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Row(
children: [
Container(
color: Colors.blue[300],
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: () => _beamerKey.currentState.routerDelegate
.beamToNamed('/books'),
child: Text('Books'),
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () => _beamerKey.currentState.routerDelegate
.beamToNamed('/articles'),
child: Text('Articles'),
),
],
),
),
Container(width: 1, color: Colors.blue),
Expanded(
child: Beamer(
key: _beamerKey,
beamLocations: [
BooksLocation(),
ArticlesLocation(),
],
),
),
],
),
);
}
}
Integration with Navigation UI Packages #
- Animated Rail Example, with animated_rail package.
- ... (contributions are very welcome; add your suggestion here or make a PR)
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 ofNavigator
pages androuteInformationParser
that decides which URI corresponds to whichRouter
state/configuration, in our case -BeamLocation
.
Here you use the Beamer implementation of those - BeamerRouterDelegate
and BeamerRouteInformationParser
, to which you pass your BeamLocation
s.
class MyApp extends StatelessWidget {
final routerDelegate = BeamerRouterDelegate(
beamLocations: [
HomeLocation(),
BooksLocation(),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: BeamerRouteInformationParser(),
backButtonDispatcher:
BeamerBackButtonDispatcher(delegate: routerDelegate),
);
}
}
Deeper in the Tree #
Similar to above example, but we use RootRouterDelegate
instead of BeamerRouterDelegate
. Then, we have 2 options:
- provide
homeBuilder
toRootRouterDelegate
which will serve the same role asMaterialApp.home
. This is useful when you need a simple app with some navigation bar.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: BeamerRouteInformationParser(),
routerDelegate: RootRouterDelegate(
homeBuilder: (context, uri) => Scaffold(
body: Beamer(
beamLocations: _beamLocations,
),
...
),
),
...
);
}
}
- provide
beamLocations
toRootRouterDelegate
as we would toBeamerRouterDelegate
and haveBeamer
somewhere deep within those locations (see nested navigation example).
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: BeamerRouteInformationParser(),
routerDelegate: RootRouterDelegate(
beamLocations: [
HomeLocation(),
BooksLocation(),
ArticlesLocation(),
],
),
...
);
}
}
General Notes #
-
When extending
BeamLocation
, two methods need to be implemented;pathBlueprints
andpagesBuilder
.pagesBuilder
returns a stack of pages that will be built byNavigator
when you beam there, andpathBlueprints
is there for Beamer to decide whichBeamLocation
corresponds to which URI.BeamLocation
keeps query and path parameters from URI in itsBeamState
. The:
is necessary inpathBlueprints
if you might get path parameter from browser.
-
BeamPage
's child is an arbitraryWidgets
that represent your app screen / page.key
is important forNavigator
to optimize rebuilds. This should be a unique value for "page state".BeamPage
createsMaterialPageRoute
, but you can extendBeamPage
and overridecreateRoute
to make your own implementation instead.
Migrating #
From 0.9 to 0.10 #
BeamLocation
constructor now takes onlyBeamState state
. (there's no need to define special constructors and callsuper
if you usebeamToNamed
)- most of the attributes that were in
BeamLocation
are now inBeamLocation.state
. When accessing them throughBeamLocation
;pathParameters
is nowstate.pathParameters
queryParameters
is nowstate.queryParameters
data
is nowstate.data
pathSegments
is nowstate.pathBlueprintSegments
uri
is nowstate.uri
From 0.7 to 0.8 #
- rename
pages
topagesBuilder
inBeamLocation
s - pass
beamLocations
toBeamerRouterDelegate
instead ofBeamerRouteInformationParser
. See Usage
From 0.4 to 0.5 #
- instead of wrapping
MaterialApp
withBeamer
, use*App.router()
String BeamLocation.pathBlueprint
is nowList<String> BeamLocation.pathBlueprints
BeamLocation.withParameters
constructor is removed and all parameters are handled with 1 constructor. See example if you needsuper
.BeamPage.page
is now calledBeamPage.child
Help and Chat #
For any problems, questions, suggestions, fun,... join us at Discord
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