Beamer Documentation
repository·master·Indexed 20 days ago
https://github.com/slovnicki/beamerA routing solution for Flutter that simplifies the implementation of Navigator 2.0 (Router API), designed for complex, nested, and multi-platform navigation. This repository includes a Mason brick for scaffolding BeamLocation classes and various examples demonstrating BeamGuards, deep linking, and integration with state management libraries like flutter_bloc and Riverpod.
What's inside slovnicki-beamer
- Beamer is a Flutter package designed to handle application routing across all platforms. It leverages the Flutter Router API and manages the underlying logic, enabling developers to implement complex navigation scenarios such as nested navigation, bottom navigation, and multiple independent routers (multiple Beamers) with ease.
Preserve Navigation State between App Launches
masterTo ensure users return to their exact previous location after a restart, follow this pattern:
1. Track Locations
Maintain a navigation state provider that stores:
booksLocation: The last known location within the Books tab.articlesLocation: The last known location within the Articles tab.lastLocation: The overall last known location of the app.
2. Listen to Route Changes
In your root
BeamerDelegate, define arouteListenerfunction. This function should be called every time the app location changes, updating the navigation state provider and persisting the values to a repository (e.g., using shared preferences).3. Restore State on Startup
When the app restarts, read the persisted locations from your provider. Because nested
BeamerDelegates must reside within aStateclass to function correctly with Flutter's lifecycle, pass these locations into theStatevia a constructor.Navigate back: Upward vs Reverse Chronological
masterBeamer supports two distinct types of reverse navigation:
- Upward (Pop): Navigates to a previous page in the current stack. This is standard
Navigatorbehavior (e.g., clicking a back button in anAppBar). UseNavigator.of(context).maybePop(). - Reverse Chronological (Beam Back): Navigates to the previous state in the
beamingHistory. This is useful for deep-linking scenarios where the previous state might not be the immediate parent in the current stack. UseBeamer.of(context).beamBack().
// Upward navigation (pop) Navigator.of(context).maybePop(); // Reverse chronological navigation (beam back) Beamer.of(context).beamBack();- Upward (Pop): Navigates to a previous page in the current stack. This is standard
Implement Custom State for BeamLocation
masterYou can use any class as a state for a
BeamLocation(for example, aChangeNotifier). To make a custom state compatible with Beamer, the class must implementRouteInformationSerializable. This interface requires implementing:fromRouteInformationtoRouteInformation
Using a custom state allows you to trigger updates in your
BeamLocationby modifying the state object directly.onTap: () { final state = context.currentBeamLocation.state as MyState; state.selectedBookId = 3; },Use BeamInterceptors to dynamically manage navigation
masterInterceptors in Beamer allow you to intercept navigation requests. UnlikeBeamGuards, which are typically static,BeamInterceptorscan be dynamically added to or removed from aBeamerDelegateduring the application lifecycle. This is useful for scenarios where navigation rules change based on user state, authentication, or specific app flows.Implement Nested Navigation with multiple Beamers
masterBeamer supports nested navigation by placing
Beamerwidgets anywhere in the widget tree. This is useful for scenarios like bottom navigation bars where the navigation state of the content area should be independent of the bar itself.When using multiple
Beamers,BeamerDelegatehas specific properties to manage parent-child synchronization:initializeFromParent: (Defaulttrue) If true, the childBeamerDelegateinitializes itsBeamStackfrom the parent's configuration.updateFromParent: (Defaulttrue) If true, the child listens to the parent's configuration updates.updateParent: Allows the child to update the parent (e.g., to syncbeamingHistoryfor theBeamerBackButtonDispatcher).
class HomeScreen extends StatelessWidget { final _beamerKey = GlobalKey<BeamerState>(); final _routerDelegate = BeamerDelegate( stackBuilder: BeamerStackBuilder( beamStacks: [ BooksStack(), ArticlesStack(), ], ), ); @override Widget build(BuildContext context) { return Scaffold( body: Beamer( key: _beamerKey, routerDelegate: _routerDelegate, ), bottomNavigationBar: BottomNavigationBarWidget(beamerKey: _beamerKey), ); } }Use BeamPage keys to optimize Navigator rebuilds
masterWhen beaming to a new location, the
Navigatorcompares the new list of pages with the old one using theirkeys.- Importance of Keys: If two pages have equal
keys (includingnull == null), theNavigatortreats them as the same page and avoids rebuilding or replacing it. - Requirement: You should always set a
BeamPage.key, typically using aValueKey. - Warning: If you do not set a
key, callingBeamer.of(context).beamToNamed('/somewhere')may result in no UI change because theNavigatorincorrectly assumes the newBeamPageis identical to the existing one.
BeamPage( key: ValueKey('unique_page_id'), child: MyWidget(), )- Importance of Keys: If two pages have equal
How BeamStack and BeamState work together
masterBeamer uses
BeamStacks to architecturally separate different parts of an application (e.g., aBooksStackfor book-related pages and anArticlesStackfor article-related pages).A
BeamStackis an abstract class that manages a specific stack of pages. It has three primary responsibilities:pathPatterns: Defines which URIs the stack can handle.buildPages: Defines how to construct theList<BeamPage>for theNavigatorbased on the current state.state: Maintains astateobject that links the path patterns to the page construction.
BeamStateis a built-in state implementation that provides access to URI attributes likepathParametersandqueryParameters.class BooksStack extends BeamStack<BeamState> { @override List<Pattern> get pathPatterns => ['/books/:bookId']; @override List<BeamPage> buildPages(BuildContext context, BeamState state) { final pages = [const BeamPage(key: ValueKey('home'), child: HomeScreen())]; if (state.uri.pathSegments.contains('books')) { pages.add(const BeamPage(key: ValueKey('books'), child: BooksScreen())); } final String? bookIdParameter = state.pathParameters['bookId']; if (bookIdParameter != null) { pages.add(BeamPage( key: ValueKey('book-$bookIdParameter'), title: 'Book #$bookIdParameter', child: BookDetailsScreen(bookId: int.tryParse(bookIdParameter)), )); } return pages; } }Understand the BeamLocation abstraction
masterA
BeamLocationis the core construct in Beamer. It represents a state of a stack of one or more pages and is used to architecturally separate different parts of an application (e.g., aBooksLocationfor book-related routes and anArticlesLocationfor article-related routes).A
BeamLocationhas three primary responsibilities:pathPatterns: Defines which URIs the location can handle.buildPages: Defines how to construct the stack ofBeamPageobjects based on the current state.state: Maintains a state object that links the path patterns to the page building logic.
class BooksLocation extends BeamLocation<BeamState> { @override List<Pattern> get pathPatterns => ['/books/:bookId']; @override List<BeamPage> buildPages(BuildContext context, BeamState state) { // ... implementation } }Configure a Global Login Guard with BeamGuard and Riverpod
masterTo protect your application routes, define a root
BeamerDelegatethat includes aBeamGuard. This guard can access Riverpod providers to check authentication status.Implementation Steps
- Define the root
BeamerDelegatewith routes like/home/*and/login. - Configure the
BeamGuardto intercept any route that is not/login. - Inside the guard, read your authentication state provider. If the user is not signed in, use the guard to redirect (beam) the user to the
/loginpage.
Note on Riverpod Access: To read Riverpod providers inside the
BeamerDelegate(outside of aBuildContext), initialize yourProviderContainerin themain()function and define theBeamerDelegatewithin that scope.- Define the root
Extend BeamStack by implementing pathPatterns and buildPages
masterTo extend
BeamStack, you must implement two methods:pathPatterns: Returns a list of patterns (e.g.,['/books/:bookId']) used by Beamer to determine whichBeamStackhandles which URI. Use the:syntax for path parameters if they are expected from the browser.buildPages: Returns a stack ofBeamPageobjects that theNavigatorwill build when beaming to that location.
BeamStackautomatically preserves query and path parameters from the URI within itsBeamState.class MyStack extends BeamStack { @override List<Pattern> get pathPatterns => ['/my-path/:id']; @override List<BeamPage> buildPages(BeamState state) { return [ BeamPage( key: ValueKey(state.pathParameters['id']), child: MyWidget(id: state.pathParameters['id']), ), ]; } }Setup Beamer in a Flutter App
masterTo integrate Beamer, use the
.routerconstructor of yourMaterialApp(or similar widget). You must provide aBeamerParserfor therouteInformationParserand aBeamerDelegatefor therouterDelegate.There are three ways to configure the
stackBuilderinBeamerDelegate:- Custom Function: A manual function that returns a
BeamStackbased onRouteInformation. BeamerStackBuilder: Automatically selects aBeamStackfrom a provided list based onpathPatterns.RoutesStackBuilder: A simplified approach using a map of routes, which removes the need for customBeamStackclasses but offers less customization.
// Option 1: Custom stackBuilder function final routerDelegate = BeamerDelegate( stackBuilder: (routeInformation, _) { if (routeInformation.uri.path.contains('books')) { return BooksStack(routeInformation); } return HomeStack(routeInformation); }, ); // Option 2: Using BeamerStackBuilder with a list final routerDelegate = BeamerDelegate( stackBuilder: BeamerStackBuilder( beamStacks: [ HomeStack(), BooksStack(), ], ), ); // Option 3: Using RoutesStackBuilder with a map final routerDelegate = BeamerDelegate( stackBuilder: RoutesStackBuilder( routes: { '/': (context, state, data) => HomeScreen(), '/books': (context, state, data) => BooksScreen(), '/books/:bookId': (context, state, data) => BookDetailsScreen(bookId: state.pathParameters['bookId']), }, ), ); // Integration into MaterialApp @override Widget build(BuildContext context) { return MaterialApp.router( routerDelegate: routerDelegate, routeInformationParser: BeamerParser(), backButtonDispatcher: BeamerBackButtonDispatcher(delegate: routerDelegate), ); }- Custom Function: A manual function that returns a