AutoRoute Documentation

repository·master·Indexed 23 days ago

https://github.com/milad-akarie/auto_route_library

A Flutter navigation package providing strongly-typed argument passing, effortless deep-linking, and simplified route setup. It supports both code generation via auto_route_generator and manual setup using NamedRouteDef. Key features include nested navigation, tab navigation via AutoTabsRouter and AutoTabsScaffold, and integration with LeanBuilder for faster incremental builds.

Tokens
19K
Snippets
42
Records
97
Agent score
80%

What's inside AutoRoute

  1. Understand Generated Routes (PageRouteInfo)

    master

    For every AutoRoute declared, the generator produces a PageRouteInfo object. These objects provide strongly-typed access to page arguments extracted from the page's constructor, acting as a type-safe way to pass data during navigation.

    // Example of a generated route class
    class BookListRoute extends PageRouteInfo {
      const BookListRoute({
        List<PagerouteInfo>? children,
      }) : super(name, initialChildren: children);
    
      static const String name = 'BookListRoute';
      static const PageInfo page = PageInfo(name, builder: (...));
    }
  2. How Router Scoping and Accessing Controllers work

    master

    Each AutoRouter widget creates a new routing scope in the widget tree.

    • AutoRouter.of(context): Returns the closest StackRouter in the current scope. If called from a root-level page, it returns the root controller. If called from a nested page, it returns the nearest parent router.
    • router.parent<T>(): Allows accessing a parent router by specifying its type (e.g., StackRouter or TabsRouter).
    • router.root: Returns the root StackRouter regardless of current depth.
    • context.innerRouterOf<T>(routeName): Accesses an inner router from outside its scope using the route's name.
    • GlobalKey: You can also access an inner router using a GlobalKey<AutoRouterState> assigned to an AutoRouter widget.
  3. Implement nested navigation

    master

    Nested navigation allows you to build an inner router inside a page of another router.

    1. Define nested routes

    In your AppRouter configuration, use the children property of an AutoRoute to define sub-routes.

    2. Render the nested routes

    To display the child routes, you must include an AutoRouter widget in the parent page's build method. This widget acts as an outlet where the child pages will be rendered.

    3. Initial/Default child routes

    To show a specific child route when the parent route is first accessed (e.g., at /dashboard), you can:

    • Give the child an empty path: path: ''.
    • Use a RedirectRoute to redirect the empty path to a specific child.

    4. Shell Routes

    A "Shell Route" is a page that contains an AutoRouter widget to render nested content. You can create one by:

    • Extending AutoRouter directly.
    • Using the EmptyShellRoute helper for routes that don't require code generation.
    // 1. Configuration
    @AutoRouterConfig(replaceInRouteName: 'Page,Route')
    class AppRouter extends RootStackRouter {
      @override
      List<AutoRoute> get routes => [
        AutoRoute(
          path: '/dashboard',
          page: DashboardRoute.page,
          children: [
            AutoRoute(path: '', page: UsersRoute.page), // Initial child
            AutoRoute(path: 'posts', page: PostsRoute.page),
          ],
        ),
      ];
    }
    
    // 2. The Parent Page (Shell)
    class DashboardPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          children: [
            // ... navigation links ...
            Expanded(
              child: AutoRouter(), // The outlet for nested routes
            ),
          ],
        );
      }
    }
    
    // 3. Creating a Shell Route via extension
    @RoutePage()
    class MyShellPage extends AutoRouter {
       const MyShellPage({Key? key}) : super(key: key);
    }
    
    // 4. Creating a Shell Route without code generation
    const BooksTab = EmptyShellRoute('BooksTab');
    context.push(BooksTab());
  4. Re-evaluate routes when auth state changes

    master

    If a user's authentication state changes while they are already on a private page, you need the router to re-evaluate the stack. This is achieved using the reevaluateListenable property in router.config.

    1. Create a Listenable (e.g., a ChangeNotifier) that notifies when the state changes.
    2. Pass it to router.config(reevaluateListenable: yourListenable).
    3. When the listenable notifies, AutoRouteGuard.onNavigation() will be re-called for all guards.

    Note on Re-evaluation: When the stack is re-evaluated, the entire hierarchy is re-pushed. To prevent continuous re-pushing after a successful login/action, use resolver.resolveNext(bool, reevaluateNext: false) instead of resolver.next().

    // 1. Define a listenable
    class AuthProvider extends ChangeNotifier {
      bool _isLoggedIn = false;
      void login() { _isLoggedIn = true; notifyListeners(); }
    }
    
    // 2. Pass to router config
    MaterialApp.router(
      routerConfig: _appRouter.config(
        reevaluateListenable: authProvider,
      ),
    );
    
    // 3. Use resolveNext to stop re-evaluation loop
    @override
    void onNavigation(NavigationResolver resolver, StackRouter router) async {
      if (authProvider.isAuthenticated) {
        resolver.next();
      } else {
        resolver.redirectUntil(
          WebLoginRoute(onResult: (didLogin) {
            resolver.resolveNext(didLogin, reevaluateNext: false);
          }),
        );
      }
    }
  5. How deep-linking to non-nested routes works

    master

    AutoRoute can build a navigation stack from a linear list of routes if they are ordered correctly and can be matched as prefixes (e.g., / is a prefix of /products, which is a prefix of /products/:id).

    When receiving a deep-link like /products/123, AutoRoute will add all matching prefix routes to the stack.

    Requirements and Constraints:

    • includePrefixMatches must be true in the root config (default is !kWeb) or when using pushNamed, navigateNamed, or replaceNamed.
    • A full match must be found at the end of the chain; if no full match is found, no prefix matches are included.
    • Routes with fullMatch: true cannot be used as prefix matches.
    • Order matters: if a more specific route (like /products/:id) appears before a less specific one (like /products) in the list, the less specific one will not be included in the stack.
  6. Migrate router configuration to v6 using @AutoRouterConfig

    master

    In version 6.0, specific router annotations like @MaterialAutoRouter, @CupertinoAutoRouter, or @AdaptiveAutoRouter have been replaced by a single @AutoRouterConfig() annotation.

    Instead of passing the routes list directly into the annotation, you must now extend the generated router class (prefixed with $) and override the routes getter. To specify the UI platform style (Material, Cupertino, or Adaptive), override the defaultRouteType getter.

    @AutoRouterConfig()
    class AppRouter extends $AppRouter {
    
      @override
      RouteType get defaultRouteType => RouteType.material(); //.cupertino, .adaptive ..etc
    
      @override
      List<AutoRoute> get routes => [
        // routes go here
      ];
    }
  7. Implement Tab Navigation with AutoTabsRouter

    master

    For mobile applications requiring tabbed navigation, use AutoTabsRouter. Unlike a standard AutoRouter (which is a shortcut for AutoStackRouter and manages a stack of pages), AutoTabsRouter preserves the state of offstage routes and allows for custom transition animations.

    By default, tab routes are lazily loaded, though this can be disabled. You can use the builder property to access the TabsRouter controller via AutoTabsRouter.of(context) to manage the active index.

    class DashboardPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return AutoTabsRouter(
          // list of your tab routes
          // routes used here must be declared as children
          routes: const [
            UsersRoute(),
            PostsRoute(),
            SettingsRoute(),
          ],
          transitionBuilder: (context,child,animation) => FadeTransition(
                opacity: animation,
                child: child,
              ),
          builder: (context, child) {
            final tabsRouter = AutoTabsRouter.of(context);
            return Scaffold(
              body: child,
              bottomNavigationBar: BottomNavigationBar(
                currentIndex: tabsRouter.activeIndex,
                onTap: (index) {
                  tabsRouter.setActiveIndex(index);
                },
                items: [
                  BottomNavigationBarItem(label: 'Users', ...),
                  BottomNavigationBarItem(label: 'Posts', ...),
                  BottomNavigationBarItem(label: 'Settings', ...),
                ],
              ),
            );
          },
        );
      }
    }
  8. Implement Tabs using PageView or TabBar

    master

    You can use specialized constructors for AutoTabsRouter to integrate with Flutter's PageView or TabBar widgets:

    • AutoTabsRouter.pageView: Provides a controller in the builder to sync with a PageView.
    • AutoTabsRouter.tabBar: Provides a controller in the builder to sync with a TabBar.
    // Using PageView
    AutoTabsRouter.pageView(
      routes: [
        BooksTab(),
        ProfileTab(),
        SettingsTab(),
      ],
      builder: (context, child, _) {
        final tabsRouter = AutoTabsRouter.of(context);
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabsRouter.activeIndex,
            onTap: tabsRouter.setActiveIndex,
            items: [...],
          ),
        );
      },
    );
    
    // Using TabBar
    AutoTabsRouter.tabBar(
      routes: [
        BooksTab(),
        ProfileTab(),
        SettingsTab(),
      ],
      builder: (context, child, controller) {
        final tabsRouter = AutoTabsRouter.of(context);
        return Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              controller: controller,
              tabs: const [
                Tab(text: '1', icon: Icon(Icons.abc)),
                Tab(text: '2', icon: Icon(Icons.abc)),
                Tab(text: '3', icon: Icon(Icons.abc)),
              ],
            ),
          ),
          body: child,
        );
      },
    );
  9. Include routes from external or micro packages

    master

    To use routes defined in a dependency (micro package), you have two options:

    1. Individual inclusion: Add specific routes from the micro package directly to your main router's routes list.
    2. Merging routers: Declare a router inside the micro package, then spread its routes into your main router using the spread operator (...).

    Tip: You can export the micro router from your app_router.dart file so that your application code only needs to import the main router file.

      final myMicroRouter = MyMicroRouter();
    
      @override
      List<AutoRoute> get routes => [
            AutoRoute(page: HomeRoute.page, initial: true),
            /// use micro routes individually
            AutoRoute(page: RouteFromMicroPackage.page),
            /// or merge all routes from micro router
            ...myMicroRouter.routes,
          ];
  10. Use AutoRoute without code generation

    master

    You can use AutoRoute without code generation by using NamedRouteDef. This allows you to provide an inline page builder function instead of a generated page. You must provide a name for the route to enable navigation by name or path.

    Declaring Named Routes

    You can use NamedRouteDef or the shorthand .named() extension on the routes list.

    Navigate using the NamedRoute class to match by name, or use pushPath, replacePath, or navigatePath to match by path string.

    // Declaring routes
    routes: [
      .named('HomeRoute', (ctx, data) => HomePage()),
      NamedRouteDef(
        name: 'BookDetailsRoute',
        path: '/books/:id',
        builder: (context, data) {
          return BookDetailsPage(id: data.params.getInt('id'));
        },
      ),
    ];
    
    // Navigating by name
    router.push(NamedRoute('BookDetailsRoute', params: {'id': 1}));
    router.push(.named('BookDetailsRoute', params: {'id': 1}));
    
    // Navigating by path
    router.pushPath('/books/1');
  11. Implement empty routes in v6

    master

    The EmptyRoutePage class has been removed in version 6.0. To create a route that acts as a sub-router or an empty container, annotate a class that extends the AutoRouter widget with @RoutePage().

    @RoutePage(name: 'ProductsRouter')
    class ProductsRouterPage extends AutoRouter {}
    
    // In your router configuration:
    AutoRoute(page: ProductsRouter.page)