Routemaster

repository·main·Indexed 43 days ago

https://github.com/tomgilder/routemaster

A declarative, path-based router for Flutter that wraps the native Router API. It simplifies complex routing scenarios including nested tabs, path parameters, query parameters, guarded routes, and runtime route swapping using RoutemasterDelegate and RoutemasterParser.

Tokens
2.8K
Snippets
13
Records
13
Agent score
6%

What's inside Routemaster

  1. Implement Tabs with TabPage and CupertinoTabPage

    main

    Routemaster supports nested navigation for tabs.

    For Material Tabs:

    1. Define a TabPage in your RouteMap. Provide a child (the tab bar container) and a list of paths that belong to this tab.
    2. In your tab container widget, use TabPage.of(context) to access the controller and stacks.
    3. Use PageStackNavigator(stack: stack) to render the content for each tab.

    For Cupertino Tabs:

    1. Define a CupertinoTabPage in your RouteMap.
    2. In your tab container widget, use CupertinoTabPage.of(context) to access the controller and tabBuilder.
    3. Use CupertinoTabScaffold to build the UI.
    // TabPage Setup
    RouteMap(
      routes: {
        '/': (route) => TabPage(
              child: HomePage(),
              paths: ['/feed', '/settings'],
            ),
        '/feed': (route) => MaterialPage(child: FeedPage()),
        '/settings': (route) => MaterialPage(child: SettingsPage()),
      },
    )
    
    // HomePage implementation for TabPage
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final tabPage = TabPage.of(context);
        return Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              controller: tabPage.controller,
              tabs: [Tab(text: 'Feed'), Tab(text: 'Settings')],
            ),
          ),
          body: TabBarView(
            controller: tabPage.controller,
            children: [
              for (final stack in tabPage.stacks) PageStackNavigator(stack: stack),
            ],
          ),
        );
      }
    }
  2. Swap routing maps at runtime

    main

    Because routesBuilder in RoutemasterDelegate provides a BuildContext, you can use state management (like Provider) to return different RouteMap instances based on application state (e.g., whether a user is logged in).

    MaterialApp.router(
      routerDelegate: RoutemasterDelegate(
        routesBuilder: (context) {
          final appState = Provider.of<AppState>(context);
          return appState.isLoggedIn ? loggedInMap : loggedOutMap;
        },
      ),
      routeInformationParser: RoutemasterParser(),
    );
  3. Install and set up Routemaster in a Flutter app

    main

    To use Routemaster, integrate it into your MaterialApp.router by providing a RoutemasterDelegate with a routesBuilder and a RoutemasterParser. The routesBuilder takes a BuildContext and returns a RouteMap containing your application's route definitions.

    MaterialApp.router(
      routerDelegate: RoutemasterDelegate(
        routesBuilder: (context) => RouteMap(routes: {
          '/': (routeData) => MaterialPage(child: PageOne()),
          '/two': (routeData) => MaterialPage(child: PageTwo()),
        }),
      ),
      routeInformationParser: RoutemasterParser(),
    )
  4. Customize the iOS Launch Screen assets

    main

    To change the image displayed during the app's launch on iOS, you can use one of two methods:

    1. Direct File Replacement: Replace the existing image files located in the example/book_store/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.
    2. Xcode Interface:
      • Open the iOS workspace using open ios/Runner.xcworkspace.
      • In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
      • Drag and drop your desired images into the asset catalog to replace the launch screen assets.
    open ios/Runner.xcworkspace
  5. Enable Hero animations in nested navigators

    main

    While Hero animations work automatically on the top-level navigator, you must wrap any PageStackNavigator used in a child navigator with a HeroControllerScope to enable them.

    HeroControllerScope(
      controller: MaterialApp.createMaterialHeroController(),
      child: PageStackNavigator(
        stack: pageStack,
      )
    )
  6. Customize iOS Launch Screen Assets

    main

    To change the launch screen image for the iOS version of your application, you can use one of two methods:

    1. Direct File Replacement: Replace the existing image files directly within the example/simple_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.
    2. Xcode Interface:
      • Open the iOS workspace using open ios/Runner.xcworkspace.
      • In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
      • Drag and drop your desired images into the asset catalog to replace the old ones.
    open ios/Runner.xcworkspace
  7. Implement guarded routes and redirects

    main

    You can control access to routes by returning different widgets or Redirect objects based on logic (e.g., authentication status).

    • Show a 404/Custom page: Return a different widget (like NotFound()) if validation fails. This does not change the URL.
    • Redirect (URL change): Return Redirect('/path') to change the current URL to a new one.
    • Redirect (No URL change): Return a different widget (like CustomNoAccessPage()) to show a specific view without changing the URL.
    • Route Redirection: Use Redirect('/path') within a route definition to map one path to another.
    // Redirecting a route
    '/one': (routeData) => MaterialPage(child: PageOne()),
    '/two': (routeData) => Redirect('/one'),
    
    // Guarded route with Redirect
    '/protected-route': (route) => 
        canUserAccessPage()
          ? MaterialPage(child: ProtectedPage())
          : Redirect('/no-access'),
  8. Access current path information within a widget

    main

    You can retrieve information about the current route using RouteData.of(context). This provides access to the full path, path parameters, and query parameters.

    RouteData.of(context).path; // Full path: '/product/123?query=param'
    RouteData.of(context).pathParameters; // Map: {'id': '123'}
    RouteData.of(context).queryParameters; // Map: {'query': 'param'}
  9. Use query parameters in routes

    main

    Query parameters (e.g., ?query=hello) are automatically parsed and accessible via the queryParameters map in the RouteData object.

    // Path '/search?query=hello' results in SearchPage(query: 'hello')
    RouteMap(routes: {
      '/search': (route) => MaterialPage(
            child: SearchPage(query: route.queryParameters['query']),
          ),
    })
  10. Listen to route changes with RoutemasterObserver

    main

    Extend RoutemasterObserver to listen to navigation events. RoutemasterObserver extends NavigatorObserver and receives all nested Navigator events. You can override didPop for standard navigator events or didChangeRoute for Routemaster-specific route data changes.

    class MyObserver extends RoutemasterObserver {
      @override
      void didPop(Route route, Route? previousRoute) {
        print('Popped a route');
      }
    
      @override
      void didChangeRoute(RouteData routeData, Page page) {
        print('New route: ${routeData.path}');
      }
    }
    
    // Register in MaterialApp.router
    routerDelegate: RoutemasterDelegate(
      observers: [MyObserver()],
      routesBuilder: (_) => routeMap,
    ),
  11. Navigate between pages using Routemaster.of(context)

    main

    Use Routemaster.of(context) to perform navigation from within a widget tree. You can use .push() to add a new route to the stack or .replace() to swap the current route. Paths can be absolute (starting with /) or relative.

    // Absolute paths
    Routemaster.of(context).push('/absolute-path');
    Routemaster.of(context).replace('/absolute-path');
    
    // Relative paths
    Routemaster.of(context).push('relative-path');
    Routemaster.of(context).replace('relative-path');
  12. Handle unknown routes with onUnknownRoute

    main

    Define a fallback page for any URL that does not match your RouteMap by using the onUnknownRoute property in RouteMap.

    RouteMap(
        onUnknownRoute: (route, context) {
            return MaterialPage(child: NotFoundPage());
        },
        routes: {
            '/': (_) => MaterialPage(child: HomePage()),
        },
    )