Beamer Documentation

repository·master·Indexed 20 days ago

https://github.com/slovnicki/beamer

A 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.

Tokens
18.1K
Snippets
69
Records
92
Agent score
69%

What's inside slovnicki-beamer

  1. Overview of Beamer for Flutter Routing

    master
    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.
  2. Preserve Navigation State between App Launches

    master

    To 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 a routeListener function. 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 a State class to function correctly with Flutter's lifecycle, pass these locations into the State via a constructor.

  3. Navigate back: Upward vs Reverse Chronological

    master

    Beamer supports two distinct types of reverse navigation:

    1. Upward (Pop): Navigates to a previous page in the current stack. This is standard Navigator behavior (e.g., clicking a back button in an AppBar). Use Navigator.of(context).maybePop().
    2. 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. Use Beamer.of(context).beamBack().
    // Upward navigation (pop)
    Navigator.of(context).maybePop();
    
    // Reverse chronological navigation (beam back)
    Beamer.of(context).beamBack();
  4. Implement Custom State for BeamLocation

    master

    You can use any class as a state for a BeamLocation (for example, a ChangeNotifier). To make a custom state compatible with Beamer, the class must implement RouteInformationSerializable. This interface requires implementing:

    • fromRouteInformation
    • toRouteInformation

    Using a custom state allows you to trigger updates in your BeamLocation by modifying the state object directly.

    onTap: () {
      final state = context.currentBeamLocation.state as MyState;
      state.selectedBookId = 3;
    },
  5. Use BeamInterceptors to dynamically manage navigation

    master
    Interceptors in Beamer allow you to intercept navigation requests. Unlike BeamGuards, which are typically static, BeamInterceptors can be dynamically added to or removed from a BeamerDelegate during the application lifecycle. This is useful for scenarios where navigation rules change based on user state, authentication, or specific app flows.
  6. Implement Nested Navigation with multiple Beamers

    master

    Beamer supports nested navigation by placing Beamer widgets 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, BeamerDelegate has specific properties to manage parent-child synchronization:

    • initializeFromParent: (Default true) If true, the child BeamerDelegate initializes its BeamStack from the parent's configuration.
    • updateFromParent: (Default true) If true, the child listens to the parent's configuration updates.
    • updateParent: Allows the child to update the parent (e.g., to sync beamingHistory for the BeamerBackButtonDispatcher).
    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),
        );
      }
    }
  7. Use BeamPage keys to optimize Navigator rebuilds

    master

    When beaming to a new location, the Navigator compares the new list of pages with the old one using their keys.

    • Importance of Keys: If two pages have equal keys (including null == null), the Navigator treats them as the same page and avoids rebuilding or replacing it.
    • Requirement: You should always set a BeamPage.key, typically using a ValueKey.
    • Warning: If you do not set a key, calling Beamer.of(context).beamToNamed('/somewhere') may result in no UI change because the Navigator incorrectly assumes the new BeamPage is identical to the existing one.
    BeamPage(
      key: ValueKey('unique_page_id'),
      child: MyWidget(),
    )
  8. How BeamStack and BeamState work together

    master

    Beamer uses BeamStacks to architecturally separate different parts of an application (e.g., a BooksStack for book-related pages and an ArticlesStack for article-related pages).

    A BeamStack is an abstract class that manages a specific stack of pages. It has three primary responsibilities:

    1. pathPatterns: Defines which URIs the stack can handle.
    2. buildPages: Defines how to construct the List<BeamPage> for the Navigator based on the current state.
    3. state: Maintains a state object that links the path patterns to the page construction.

    BeamState is a built-in state implementation that provides access to URI attributes like pathParameters and queryParameters.

    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;
      }
    }
  9. Understand the BeamLocation abstraction

    master

    A BeamLocation is 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., a BooksLocation for book-related routes and an ArticlesLocation for article-related routes).

    A BeamLocation has three primary responsibilities:

    1. pathPatterns: Defines which URIs the location can handle.
    2. buildPages: Defines how to construct the stack of BeamPage objects based on the current state.
    3. 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
      }
    }
  10. Configure a Global Login Guard with BeamGuard and Riverpod

    master

    To protect your application routes, define a root BeamerDelegate that includes a BeamGuard. This guard can access Riverpod providers to check authentication status.

    Implementation Steps

    1. Define the root BeamerDelegate with routes like /home/* and /login.
    2. Configure the BeamGuard to intercept any route that is not /login.
    3. 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 /login page.

    Note on Riverpod Access: To read Riverpod providers inside the BeamerDelegate (outside of a BuildContext), initialize your ProviderContainer in the main() function and define the BeamerDelegate within that scope.

  11. Extend BeamStack by implementing pathPatterns and buildPages

    master

    To extend BeamStack, you must implement two methods:

    1. pathPatterns: Returns a list of patterns (e.g., ['/books/:bookId']) used by Beamer to determine which BeamStack handles which URI. Use the : syntax for path parameters if they are expected from the browser.
    2. buildPages: Returns a stack of BeamPage objects that the Navigator will build when beaming to that location.

    BeamStack automatically preserves query and path parameters from the URI within its BeamState.

    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']),
          ),
        ];
      }
    }
  12. Setup Beamer in a Flutter App

    master

    To integrate Beamer, use the .router constructor of your MaterialApp (or similar widget). You must provide a BeamerParser for the routeInformationParser and a BeamerDelegate for the routerDelegate.

    There are three ways to configure the stackBuilder in BeamerDelegate:

    1. Custom Function: A manual function that returns a BeamStack based on RouteInformation.
    2. BeamerStackBuilder: Automatically selects a BeamStack from a provided list based on pathPatterns.
    3. RoutesStackBuilder: A simplified approach using a map of routes, which removes the need for custom BeamStack classes 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),
      );
    }