Code Monkey home page Code Monkey logo

auto_route_library's Introduction

auto_route_logo

MIT License stars pub version Discord Badge

Buy Me A Coffee


Note: [AutoRoute-Helper] is no longer supported.

Migration guides

Pre v6 documentation

Introduction

What is AutoRoute?

It’s a Flutter navigation package, it allows for strongly-typed arguments passing, effortless deep-linking and it uses code generation to simplify routes setup. With that being said, it requires a minimal amount of code to generate everything needed for navigation inside of your App.

Why AutoRoute?

If your App requires deep-linking or guarded routes or just a clean routing setup, you'll need to use named/generated routes and you’ll end up writing a lot of boilerplate code for mediator argument classes, checking for required arguments, extracting arguments and a bunch of other stuff. * AutoRoute* does all that for you and much more.

Installation

dependencies:
 auto_route: [latest-version]

dev_dependencies:
 auto_route_generator: [latest-version]
 build_runner:

Setup And Usage

  1. Create a router class and annotate it with @AutoRouterConfig then extend "$YourClassName"
  2. Override the routes getter and start adding your routes.
@AutoRouterConfig()
class AppRouter extends $AppRouter {

 @override
 List<AutoRoute> get routes => [
   /// routes go here
 ];
}

Using part builder

To generate a part-of file simply add a part directive to your AppRouter and extend the generated private router. Note: The deferredLoading functionality does not work with part-file setup.

part 'app_router.gr.dart';

@AutoRouterConfig()
class AppRouter extends _$AppRouter {

  @override
  List<AutoRoute> get routes => [
    /// routes go here
  ];
}

Generating Routable pages

Routable pages are just simple everyday widgets annotated with @RoutePage() which allows them to be constructed by the router.

@RoutePage()
class HomeScreen extends StatefulWidget {}

Now simply run the generator

Use the [watch] flag to watch the files' system for edits and rebuild as necessary.

dart run build_runner watch

If you want the generator to run one time and exit, use

dart run build_runner build

Add the generated route to your routes list

@AutoRouterConfig(replaceInRouteName: 'Screen,Route')
class AppRouter extends $AppRouter {

  @override
  List<AutoRoute> get routes => [
    // HomeScreen is generated as HomeRoute because
    // of the replaceInRouteName property
    AutoRoute(page: HomeRoute.page),
  ];
}

Finalize the setup

After you run the generator, your router class will be generated. Then simply hook it up with your MaterialApp.

// assuming this is the root widget of your App
class App extends StatelessWidget {
  // make sure you don't initiate your router
  // inside of the build function.
  final _appRouter = AppRouter();

  @override
  Widget build(BuildContext context){
    return MaterialApp.router(
      routerConfig: _appRouter.config(),
    );
  }
}

Generated Routes

A PageRouteInfo object will be generated for every declared AutoRoute. These objects hold strongly-typed page arguments which are extracted from the page's default constructor. Think of them as string path segments on steroids.

class BookListRoute extends PageRouteInfo {
  const BookListRoute({
    List<PagerouteInfo>? children,
  }) : super(name, path: '/books', initialChildren: children);

  static const String name = 'BookListRoute';
  static const PageInfo<void> page = PageInfo<void>(name);
}

Navigating Between Screens

AutoRouter offers the same known push, pop and friends methods to manipulate the pages stack using both the generated PageRouteInfo objects and paths.

// get the scoped router by calling
AutoRouter.of(context);
// or using the extension
context.router;
// adds a new entry to the pages stack
router.push(const BooksListRoute());
// or by using paths
router.pushNamed('/books');
// removes last entry in stack and pushes provided route
// if last entry == provided route page will just be updated
router.replace(const BooksListRoute());
// or by using paths
router.replaceNamed('/books');
// pops until provided route, if it already exists in stack
// else adds it to the stack (good for web Apps).
router.navigate(const BooksListRoute());
// or by using paths
router.navigateNamed('/books');
// on Web it calls window.history.back();
// on Native it navigates you back
// to the previous location
router.back();
// adds a list of routes to the pages stack at once
router.pushAll([
  BooksListRoute(),
  BookDetailsRoute(id: 1),
]);
// This's like providing a completely new stack as it rebuilds the stack
// with the list of passed routes
// entries might just update if already exist
router.replaceAll([
  LoginRoute(),
]);
// pops the last page unless blocked or stack has only 1 entry
context.router.maybPop();
// popts the most top page of the most top router unless blocked
// or stack has only 1 entry
context.router.maybePopTop();
// keeps popping routes until predicate is satisfied
context.router.popUntil((route) => route.settings.name == 'HomeRoute');
// a simplified version of the above line
context.router.popUntilRouteWithName('HomeRoute');
// keeps popping routes until route with provided path is found
context.router.popUntilRouteWithPath('/some-path');
// pops all routes down to the root
context.router.popUntilRoot();
// removes the top most page in stack even if it's the last
// remove != pop, it doesn't respect WillPopScopes it just
// removes the entry.
context.router.removeLast();
// removes any route in stack that satisfies the predicate
// this works exactly like removing items from a regular List
// <PageRouteInfo>[...].removeWhere((r)=>)
context.router.removeWhere((route) => );
// you can also use the common helper methods from context extension to navigate
context.pushRoute(const BooksListRoute());
context.replaceRoute(const BooksListRoute());
context.navigateTo(const BooksListRoute());
context.navigateNamedTo('/books');
context.back();
context.maybePop();

Passing Arguments

That's the fun part! AutoRoute automatically detects and handles your page arguments for you, the generated route object will deliver all the arguments your page needs including path/query params.

e.g. The following page widget will take an argument of type Book.

@RoutePage()
class BookDetailsPage extends StatelessWidget {
  const BookDetailsPage({required this.book});

  final Book book;
  ...

Note: Default values are respected. Required fields are also respected and handled properly.

The generated BookDetailsRoute will deliver the same arguments to its corresponding page.

router.push
(
BookDetailsRoute
(
book
:
book
)
);

Note: All arguments are generated as named parameters regardless of their original type.

Returning Results

You can return results by either using the pop completer or by passing a callback function as an argument the same way you'd pass an object.

1. Using the pop completer

var result = await
router.push
(
LoginRoute
(
)
);

then inside of your LoginPage, pop with results

router.maybePop
(true);

as you'd notice we did not specify the result type, we're playing with dynamic values here, which can be risky and I personally don't recommend it.

To avoid working with dynamic values, we specify what type of results we expect our page to return, which is a bool value.

@RoutePage<bool>()
class LoginPage extends StatelessWidget {}

we push and specify the type of results we're expecting

var result = await
router.push<bool>
(
LoginRoute
(
)
);

and of course we pop with the same type

router.maybePop<bool>
(true);

2. Passing a callback function as an argument.

We only have to add a callback function as a parameter to our page constructor like follows:

@RoutePage()
class BookDetailsPage extends StatelessWidget {
  const BookDetailsRoute({this.book, required this.onRateBook});

  final Book book;
  final void Function(int) onRateBook;
  ...

The generated BookDetailsRoute will deliver the same arguments to its corresponding page.

context.pushRoute
(
BookDetailsRoute(
book: book,
onRateBook: (rating) {
// handle result
},
)
,
);

If you're finishing with results, make sure you call the callback function as you pop the page

onRateBook(RESULT);
context.maybePop
();

Note: Default values are respected. Required fields are also respected and handled properly.

Nested Navigation

Nested navigation means building an inner router inside of a page of another router, for example in the below diagram users page is built inside of dashboard page.

Defining nested routes is as easy as populating the children field of the parent route. In the following example UsersPage, PostsPage and SettingsPage are nested children of DashboardPage.

@AutoRouterConfig(replaceInRouteName: 'Page,Route')
class AppRouter extends $AppRouter {

@override
List<AutoRoute> get routes => [
    AutoRoute(
      path: '/dashboard',
      page: DashboardRoute.page,
      children: [
        AutoRoute(path: 'users', page: UsersRoute.page),
        AutoRoute(path: 'posts', page: PostsRoute.page),
        AutoRoute(path: 'settings', page: SettingsRoute.page),
      ],
    ),
    AutoRoute(path: '/login', page: LoginRoute.page),
  ];
}

To render/build nested routes we need an AutoRouter widget that works as an outlet or a nested router-view inside of our dashboard page.

class DashboardPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Column(
          children: [
            NavLink(label: 'Users', destination: const UsersRoute()),
            NavLink(label: 'Posts', destination: const PostsRoute()),
            NavLink(label: 'Settings', destination: const SettingsRoute()),
          ],
        ),
        Expanded(
          // nested routes will be rendered here
          child: AutoRouter(),
        ),
      ],
    );
  }
}

Note NavLink is just a button that calls router.push(destination). Now if we navigate to /dashboard/users, we will be taken to the DashboardPage and the UsersPage will be shown inside of it.

What if want to show one of the child pages at /dashboard? We can simply do that by giving the child routes an empty path '' to make initial or by setting initial to true.

AutoRoute
(
path: '/dashboard',
page: DashboardRoute.page,
children: [
AutoRoute(path: '', page: UsersRoute.page),
AutoRoute(path: 'posts', page: PostsRoute.page
)
,
]
,
)

or by using a RedirectRoute

AutoRoute
(
path: '/dashboard',
page: DashboardRoute.page,
children: [
RedirectRoute(path: '', redirectTo: 'users'),
AutoRoute(path: 'users', page: UsersRoute.page),
AutoRoute(path: 'posts', page: PostsRoute.page),
],
)

Things to keep in mind when implementing nested navigation

  1. Each router manages its own pages stack.
  2. Navigation actions like push, pop and friends are handled by the topmost router and bubble up if it couldn't be handled.

Tab Navigation

If you're working with flutter mobile, you're most likely to implement tabs navigation, that's why auto_route makes tabs navigation as easy and straightforward as possible.

In the previous example we used an AutoRouter widget to render nested child routes, AutoRouter is just a shortcut for AutoStackRouter. StackRouters manage a stack of pages inside of them, where the active/visible page is always the one on top and you'd need to pop it to see the page beneath it.

Now we can try to implement our tabs using an AutoRouter (StackRouter) by pushing or replacing a nested route every time the tab changes and that might work, but our tabs state will be lost, not to mention the transition between tabs issue, luckily auto_route comes equipped with an AutoTabsRouter, which is especially made to handle tab navigation.

AutoTabsRouter lets you switch between different routes while preserving offstage-routes state, tab routes are lazily loaded by default (can be disabled) and it finally allows to create whatever transition animation you want.

Let's change the previous example to use tab navigation.

Notice that we're not going to change anything in our routes declaration map, we still have a dashboard page that has three nested children: users, posts and settings.

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 of /dashboard
      routes: const [
        UsersRoute(),
        PostsRoute(),
        SettingsRoute(),
      ],
      transitionBuilder: (context,child,animation) => FadeTransition(
            opacity: animation,
            // the passed child is technically our animated selected-tab page
            child: child,
          ),
      builder: (context, child) {
        // obtain the scoped TabsRouter controller using context
        final tabsRouter = AutoTabsRouter.of(context);
        // Here we're building our Scaffold inside of AutoTabsRouter
        // to access the tabsRouter controller provided in this context
        //
        // alternatively, you could use a global key
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabsRouter.activeIndex,
            onTap: (index) {
              // here we switch between tabs
              tabsRouter.setActiveIndex(index);
            },
            items: [
              BottomNavigationBarItem(label: 'Users', ...),
              BottomNavigationBarItem(label: 'Posts', ...),
              BottomNavigationBarItem(label: 'Settings', ...),
            ],
          ),
        );
      },
    );
  }
}

If you think the above setup is a bit messy you could use the shipped-in AutoTabsScaffold that makes things much cleaner.

class DashboardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AutoTabsScaffold(
      routes: const [
        UsersRoute(),
        PostsRoute(),
        SettingsRoute(),
      ],
      bottomNavigationBuilder: (_, tabsRouter) {
        return BottomNavigationBar(
          currentIndex: tabsRouter.activeIndex,
          onTap: tabsRouter.setActiveIndex,
          items: const [
            BottomNavigationBarItem(label: 'Users', ...),
            BottomNavigationBarItem(label: 'Posts', ...),
            BottomNavigationBarItem(label: 'Settings', ...),
          ],
        );
      },
    );
  }
}

Using PageView

Use the AutoTabsRouter.pageView constructor to implement tabs using PageView

AutoTabsRouter.pageView
(
routes: [
BooksTab(),
ProfileTab(),
SettingsTab(),
],
builder: (context, child, _) {
final tabsRouter = AutoTabsRouter.of(context);
return Scaffold(
appBar: AppBar(
title: Text(context.topRoute.name),
leading: AutoLeadingButton()),
body: child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex
items: [
BottomNavigationBarItem(label: 'Books', ...),
BottomNavigationBarItem(label: 'Profile', ...),
BottomNavigationBarItem(label: 'Settings', ...),
],
),
);
},
); 

Using TabBar

Use the AutoTabsRouter.tabBar constructor to implement tabs using TabBar

AutoTabsRouter.tabBar
(
routes: [
BooksTab(),
ProfileTab(),
SettingsTab(),
],
builder: (context, child, controller) {
final tabsRouter = AutoTabsRouter.of(context);
return Scaffold(
appBar: AppBar(
title: Text(context.topRoute.name),
leading: AutoLeadingButton(),
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,
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex
items: [
BottomNavigationBarItem(label: 'Books',...),
BottomNavigationBarItem(label: 'Profile',...),
BottomNavigationBarItem(label: 'Settings',...),
],
),
);
},
);

Finding The Right Router

Every nested AutoRouter has its own routing controller to manage the stack inside of it and the easiest way to obtain a scoped controller is by using the BuildContext.

In the previous example, DashboardPage is a root level stack entry so calling AutoRouter.of(context) anywhere inside of it will get us the root routing controller.

AutoRouter widgets that are used to render nested routes, insert a new router scope into the widgets tree, so when a nested route calls for the scoped controller, they will get the closest parent controller in the widgets tree; not the root controller.

class Dashboard extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // this will get us the root routing controller
    AutoRouter.of(context);
    return Scaffold(
      appBar: AppBar(title: Text('Dashboard page')),
      // this inserts a new router scope into the widgets tree
      body: AutoRouter()
    );
  }
}

Here's a simple diagram to help visualize this

As you can tell from the above diagram it's possible to access parent routing controllers by calling router.parent<T>(), we're using a generic function because we have two different routing controllers: StackRouter and TabsRouter, one of them could be the parent controller of the current router and that's why we need to specify a type.

router.parent<StackRouter>
() // this returns  the parent router as a Stack Routing controller
router.parent<TabsRouter>
() // this returns athe parent router as a Tabs Routing controller

On the other hand, obtaining the root controller does not require type casting because it's always a StackRouter.

router.root // this returns the root router as a Stack Routing controller

You can obtain access to inner-routers from outside their scope using a global key

class DashboardPage extends StatefulWidget {
  @override
  _DashboardPageState createState() => _DashboardPageState();
}

class _DashboardPageState extends State<DashboardPage> {
  final _innerRouterKey = GlobalKey<AutoRouterState>();

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Column(
          children: [
            NavLink(
              label: 'Users',
              onTap: () {
                final router = _innerRouterKey.currentState?.controller;
                router?.push(const UsersRoute());
              },
            ),
            ...
          ],
        ),
        Expanded(
          child: AutoRouter(key: _innerRouterKey),
        ),
      ],
    );
  }
}

You could also obtain access to inner-routers from outside their scope without a global key, as long as they're initiated.

// assuming this's the root router
context.innerRouterOf<StackRouter>
(
UserRoute.name);
// or if we're usign an AutoTabsRouter inside of DashboardPage
context.innerRouterOf<TabsRouter>(
UserRoute
.
name
);

Accessing the DashboardPage inner router from the previous example.

class Dashboard extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dashboard'),
        actions: [
          IconButton(
            icon: Icon(Icons.person),
            onPressed: () {
              // accessing the inner router from
              // outside the scope
              final router = context.innerRouterOf<StackRouter>(DashboardRoute.name)
              router?.push(const UsersRoute());
            },
          ),
        ],
      ),
      body: AutoRouter(), // we're trying to get access to this
    );
  }
}

Navigating Without Context

To navigate without context you can simply assign your generated router to a global variable

// declare your route as a global vairable
final appRouter = AppRouter();

class MyApp extends StatefulWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: appRouter.config(),
    );
  }
}

Note: Using global variable is not recommended and is considered bad practice and most of the times you should use dependency injection instead.

Here's an example using get_it (which is just a personal favorite). You can use any dependency injection package you like.

void main(){
  // make sure you register it as a Singleton or a lazySingleton
  getIt.registerSingleton<AppRouter>(AppRouter());
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  Widget build(BuildContext context) {
    final appRouter = getIt<AppRouter>();

    return MaterialApp.router(
      routerConfig: appRouter.config(),
    );
  }
}

Now you can access to your router anywhere inside of your App without using context.

getIt<AppRouter>
().push
(...);

Note: Navigating without context is not recommended in nested navigation unless you use navigate instead of push and you provide a full hierarchy, e.g router.navigate(SecondRoute(children: [SubChild2Route()]))

Deep Linking

AutoRoute will automatically handle deep-links coming from the platform, but native platforms require some setup, see Deep linking topic in flutter documentation.

Using Deep-link Builder

Deep link builder is an interceptor for deep-links where you can validate or override deep-links coming from the platform.

In the following example we will only allow deep-links starting with /products

MaterialApp.router(
  routerConfig: _appRouter.config(
    deepLinkBuilder: (deepLink) {
      if (deepLink.path.startsWith('/products')) {
        // continute with the platfrom link
        return deepLink;
      } else {
        return DeepLink.defaultPath;
        // or DeepLink.path('/')
        // or DeepLink([HomeRoute()])
      }
    }
  ),
);

Deep Linking to non-nested Routes

AutoRoute can build a stack from a linear route list as long as they're ordered properly and can be matched as prefix, e.g / is a prefix match of /products, and /products is prefix match of /products/:id. Then we have a setup that looks something like this:

  • /
  • /products
  • /products/:id

Now, receiving this deep-link /products/123 will add all above routes to the stack. This of course requires includePrefixMatches to be true in the root config (default is true) or when using pushNamed, navigateNamed and replaceNamed.

Things to keep in mind:

  • If a full match can not finally be found, no prefix matches will be included.
  • Paths that require a full path match => AutoRoute(path:'path', fullMatch: true) will not be included as prefix matches.
  • In the above example, if /products/:id comes before /products, /products will not be included.

Declarative Navigation

To use declarative navigation with auto_route, you simply use the AutoRouter.declarative constructor and return a list of routes based on state.

AutoRouter.declarative
(
routes: (handler) => [
BookListRoute(),
if(_selectedBook != null)
BookDetailsRoute(id: _selectedBook
.
id
)
,
]
,
);

Note: The handler contains a temp-list of pending initial routes which can be read only once.

Working with Paths

Working with paths in AutoRoute is optional because PageRouteInfo objects are matched by name unless pushed as a string using the deepLinkBuilder property in root delegate or pushNamed, replaceNamed navigateNamed methods.

If you don’t specify a path it’s going to be generated from the page name e.g. BookListPage will have ‘book-list-page’ as a path, if initial arg is set to true the path will be /, unless it's relative then it will be an empty string ''.

When developing a web application or a native app that requires deep-linking, you'd probably need to define paths with clear memorable names, and that's done using the path argument in AutoRoute.

AutoRoute
(
path: '/books', page: BookListPage),

Path Parameters (dynamic segments)

You can define a dynamic segment by prefixing it with a colon

AutoRoute
(
path: '/books/:id', page: BookDetailsPage),

The simplest way to extract path parameters from path and gain access to them is by annotating constructor params with @PathParam('optional-alias') with the same alias/name of the segment.

class BookDetailsPage extends StatelessWidget {
  const BookDetailsPage({@PathParam('id') this.bookId});

  final int bookId;
  ...
}

Now writing /books/1 in the browser will navigate you to BookDetailsPage and automatically extract the bookId argument from path and inject it to your widget.

Inherited Path Parameters

To inherit a path-parameter from a parent route's path, we need to use @PathParam.inherit annotation in the child route's constructor. Let's say we have the following setup:

AutoRoute
(
path: '/product/:id',
page: ProductRoute.page,
children: [
AutoRoute(path: 'review',page: ProductReviewRoute.page),
],
)

Now ProductReviewScreen expects a path-param named id but, from the above snippet we know that the path corresponding with it. review has no path parameters, but we can inherit 'id' form the parent /product/:id like follows:

@RoutePage()
class ProductReviewScreen extends StatelessWidget {
  // the path-param 'id' will be inherited and it can not be passed
  // as a route arg by user
  const ProductReviewScreen({super.key, @PathParam.inherit('id') required String id});
}

Query Parameters

Query parameters are accessed the same way, simply annotate the constructor parameter to hold the value of the query param with @QueryParam('optional-alias') and let AutoRoute do the rest.

You could also access path/query parameters using the scoped RouteData object.

RouteData.of
(
context
)
.
pathParams;
// or using the extension
context
.
routeData
.
queryParams

Tip: if your parameter name is the same as the path/query parameter, you could use the const @pathParam or @queryParam and not pass a slug/alias.

@RoutePage()
class BookDetailsPage extends StatelessWidget {
  const BookDetailsPage({@pathParam this.id});

  final int id;
  ...
}

Redirecting Paths

Paths can be redirected using RedirectRoute. The following setup will navigate us to /books when / is matched.

<AutoRoute> [
RedirectRoute(path: '/', redirectTo: '/books'),
AutoRoute(path: '/books', page: BookListRoute.page)
,
]

When redirecting initial routes the above setup can be simplified by setting the /books path as initial and AutoRoute will automatically generate the required redirect code for you.

<AutoRoute> [
AutoRoute(path: '/books', page: BookListRoute.page, initial: true),
]

You can also redirect paths with params like follows:

<AutoRoute> [
RedirectRoute(path: 'books/:id', redirectTo: '/books/:id/details'),
AutoRoute(path: '/books/:id/details', page: BookDetailsRoute.page)
,
]

Note: RedirectRoutes are fully matched.

Wildcards

AutoRoute supports wildcard matching to handle invalid or undefined paths.

AutoRoute
(
path: '*', page: UnknownRoute.page)
// it could be used with defined prefixes
AutoRoute(path: '/profile/*', page: ProfileRoute.page)
// or it could be used with RedirectRoute
RedirectRoute(path: '*
'
,
redirectTo
:
'
/
'
)

Note: Be sure to always add your wildcards at the end of your route list because routes are matched in order.

Route Guards

Think of route guards as middleware or interceptors, routes can not be added to the stack without going through their assigned guards. Guards are useful for restricting access to certain routes.

We create a route guard by extending AutoRouteGuard from the AutoRoute package and implementing our logic inside of the onNavigation method.

class AuthGuard extends AutoRouteGuard {

  @override
  void onNavigation(NavigationResolver resolver, StackRouter router) {
    // the navigation is paused until resolver.next() is called with either
    // true to resume/continue navigation or false to abort navigation
    if(authenticated) {
      // if user is authenticated we continue
      resolver.next(true);
    } else {
        // we redirect the user to our login page
        // tip: use resolver.redirect to have the redirected route
        // automatically removed from the stack when the resolver is completed
        resolver.redirect(
          LoginRoute(onResult: (success) {
            // if success == true the navigation will be resumed
            // else it will be aborted
            resolver.next(success);
          },
        );
      );
    }
  }
}

Important: resolver.next() should only be called once.

The NavigationResolver object contains the guarded route which you can access by calling the property resolver.route and a list of pending routes (if there are any) accessed by calling resolver.pendingRoutes.

Now we assign our guard to the routes we want to protect.

AutoRoute
(
page: ProfileRoute.page, guards: [AuthGuard()
]
);

Guarding all stack-routes

You can have all your stack-routes (non-tab-routes) go through a global guard by having your router implement an AutoRouteGuard. Lets say you have an app with no publish screens, we'd have a global guard that only allows navigation if the user is authenticated or if we're navigating to the LoginRoute.

@AutoRouterConfig()
class AppRouter extends $AppRouter implements AutoRouteGuard {

  @override
  void onNavigation(NavigationResolver resolver, StackRouter router) {
    if(isAuthenticated || resolver.route.name == LoginRoute.name) {
      // we continue navigation
      resolver.next();
    } else {
        // else we navigate to the Login page so we get authenticateed

        // tip: use resolver.redirect to have the redirected route
        // automatically removed from the stack when the resolver is completed
      resolver.redirect(LoginRoute(onResult: (didLogin) => resolver.next(didLogin)));
    }
  }
  // ..routes[]
}

Using a Reevaluate Listenable

Route guards can prevent users from accessing private pages until they're logged in for example, but auth state may change when the user is already navigated to the private page, to make sure private pages are only accessed by logged-in users all the time, we need a listenable that tells the router that the auth state has changed and you need to re-evaluate your stack.

The following auth provider mock will act as our re-valuate listenable

class AuthProvider extends ChangeNotifier {
  bool _isLoggedIn = false;

  bool get isLoggedIn => _isLoggedIn;

  void login() {
    _isLoggedIn = true;
    notifyListeners();
  }

  void logout() {
    _isLoggedIn = false;
    notifyListeners();
  }
}

We simply pass an instance of our AuthProvider to reevaluateListenable inside of router.config

MaterialApp.router
(
routerConfig: _appRouter.config(
reevaluateListenable: authProvider,
),
);

Now, every time AutoProvider notifies listeners, the stack will be re-evaluated and AutoRouteGuard.onNavigation(). Methods will be re-called on all guards

In the above example, we assigned our AuthProvider to reevaluateListenable directly, that's because reevaluateListenable takes a Listenable and AuthProvider extends ChangeNotifer which is a Listenable, if your auth provider is a stream you can use reevaluateListenable: ReevaluateListenable.stream(YOUR-STREAM)

Note: When the Stack is re-evaluated, the whole existing hierarchy will be re-pushed, so if you want to stop re-evaluating routes at some point, use resolver.resolveNext(<options>) which is like resolver.next() but with more options.

@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
  if (authProvider.isAuthenticated) {
    resolver.next();
  } else {
    resolver.redirect(
      WebLoginRoute(
        onResult: (didLogin) {
          // stop re-pushing any pending routes after current
          resolver.resolveNext(didLogin, reevaluateNext: false);
        },
      ),
    );
  }
}

Wrapping Routes

In some cases we want to wrap our screen with a parent widget, usually to provide some values through context, e.g wrapping your route with a custom Theme or a Provider. To do that, simply implement AutoRouteWrapper, and have wrappedRoute(context) method return (this) as the child of your wrapper widget.

@RoutePage()
class ProductsScreen extends StatelessWidget implements AutoRouteWrapper {
  
  @override
  Widget wrappedRoute(BuildContext context) {
    return Provider(create: (ctx) => ProductsBloc(), child: this);
  }
  ...
}

Navigation Observers

Navigation observers are used to observe when routes are pushed ,replaced or popped ..etc.

We implement an AutoRouter observer by extending an AutoRouterObserver which is just a NavigatorObserver with tab route support.

class MyObserver extends AutoRouterObserver {

  @override
  void didPush(Route route, Route? previousRoute) {
    print('New route pushed: ${route.settings.name}');
  }

 // only override to observer tab routes
  @override
  void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {
    print('Tab route visited: ${route.name}');
  }

  @override
  void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) {
    print('Tab route re-visited: ${route.name}');
  }
}

Then we pass our observer to the <routerName>.config(). Important: Notice that navigatorObservers property is a builder function that returns a list of observes and the reason for that is a navigator observer instance can only be used by a single router, so unless you're using a single router or you don't want your nested routers to inherit observers, make sure navigatorObservers builder always returns fresh observer instances.

return MaterialApp.router(
routerConfig: _appRouter.config(
navigatorObservers: () => [MyObserver()],
)
,
);

The following approach won't work if you have nested routers unless they don't inherit the observers.

final _observer = MyObserver();
return MaterialApp.router(
  routerConfig: _appRouter.config(
    // this should always return new instances
    navigatorObservers: () => [_observer],
  ),
);

Every nested router can have it's own observers and inherit it's parent's.

AutoRouter(
  inheritNavigatorObservers: true, // true by default
  navigatorObservers:() => [list of observers],
);

AutoTabsRouter(
  inheritNavigatorObservers: true, // true by default
  navigatorObservers:() => [list of observers],
);

We can also make a certain screen route aware by subscribing to an AutoRouteObserver (route not router).

First we provide our AutoRouteObserver instance

return MaterialApp.router(
  routerConfig: _appRouter.config(
    navigatorObservers: () => [AutoRouteObserver()],
  ),
);

Next, we use an AutoRouteAware mixin which is a RouteAware mixin with tab support to provide the needed listeners, then subscribe to our AutoRouteObserver.

class BooksListPage extends State<BookListPage> with AutoRouteAware {
  AutoRouteObserver? _observer;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // RouterScope exposes the list of provided observers
    // including inherited observers
    _observer = RouterScope.of(context).firstObserverOfType<AutoRouteObserver>();
    if (_observer != null) {
      // we subscribe to the observer by passing our
      // AutoRouteAware state and the scoped routeData
      _observer.subscribe(this, context.routeData);
    }
  }

 @override
  void dispose() {
    super.dispose();
    // don't forget to unsubscribe from the
    // observer on dispose
    _observer.unsubscribe(this);
  }

  // only override if this is a tab page
  @override
  void didInitTabRoute(TabPageRoute? previousRoute) {}

  // only override if this is a tab page
  @override
  void didChangeTabRoute(TabPageRoute previousRoute) {}

  @override
  void didPopNext() {}

  @override
  void didPushNext() {}

  @override
  void didPush() {}

  @override
  void didPop() {}
}

AutoRouteAwareStateMixin

The above code can be simplified using AutoRouteAwareStateMixin

class BooksListPage extends State<BookListPage> with AutoRouteAwareStateMixin<BookListPage> {
  // only override if this is a tab page
  @override
  void didInitTabRoute(TabPageRoute? previousRoute) {}

  // only override if this is a tab page
  @override
  void didChangeTabRoute(TabPageRoute previousRoute) {}

  // only override if this is a stack page
  @override
  void didPopNext() {}
  
  // only override if this is a stack page
  @override
  void didPushNext() {}
}

Customizations

MaterialAutoRouter | CupertinoAutoRouter | AdaptiveAutoRouter
Property Default value Definition
replaceInRouteName [String] Page&#124Screen,Route Used to replace conventional words in generated route name (pattern, replacement)

Custom Route Transitions

To use custom route transitions use a CustomRoute and pass in your preferences. The TransitionsBuilder function needs to be passed as a static/const reference that has the same signature as the TransitionsBuilder function of the PageRouteBuilder class.

CustomRoute(
  page: LoginRoute.page,
  // TransitionsBuilders class contains a preset of common transitions builders.
  transitionsBuilder: TransitionsBuilders.slideBottom,
  durationInMilliseconds: 400,
)

Tip: Override defaultRouteType in generated router to define global custom route transitions.

You can of course use your own transitionsBuilder function, as long as it has the same function signature. The function has to take in exactly one BuildContext, Animation<Double>, Animation<Double> and a child Widget and it needs to return a Widget. Typically, you would wrap your child with one of Flutter's transition widgets as follows:

CustomRoute(
  page: ZoomInScreen, transitionsBuilder:
    (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    // you get an animation object and a widget
    // make your own transition
    return ScaleTransition(scale: animation, child: child);
  },
)

Custom Route Builder

You can use your own custom route by passing a CustomRouteBuilder function to `CustomRoute' and implement the builder function the same way we did with the TransitionsBuilder function, the most important part here is passing the page argument to our custom route.

CustomRoute(
  page: CustomPage, customRouteBuilder: (BuildContext context, Widget child, CustomPage<T> page) {
    return PageRouteBuilder(
      fullscreenDialog: page.fullscreenDialog,
      // this is important
      settings: page,
      pageBuilder: (_,__,___) => child,
    );
  },
)

Others

Including Micro/External Packages

To include routes inside of a depended-on package, that package needs to generate an AutoRouterModule that will be later consumed by the root router.

To have a package output an AutoRouterModule instead of a RootStackRouter, we need to use the AutoRouterConfig.moudle() annotation like follows

@AutoRouterConfig.module()
class MyPackageModule extends $MyPackageModule {}

Then when setting up our root router we need to tell it to include the generated module.

@AutoRouterConfig(modules: [MyPackageModule])
class AppRouter extends $AppRouter {}

Now you can use PageRouteInfos generated inside MyPackageModule.

Tip: You can add export MyPackageModule to app_router.dart, so you only import app_router.dart inside of your code.

// ...imports
export 'package:my_package/my_package_module.dart'
@AutoRouterConfig(modules: [MyPackageModule])
class AppRouter extends $AppRouter {}

Configuring builders

To pass builder configuration to auto_route_generator we need to add build.yaml file next to pubspec.yaml if not already added.

targets:
  $default:
    builders:
      auto_route_generator:auto_route_generator:
      # configs for @RoutePage() generator ...
      auto_route_generator:auto_router_generator:
      # configs for @AutoRouterConfig() generator ...

Passing custom ignore_for_file rules

You can pass custom ignore_for_file rules to the generated router by adding the following:

targets:
  $default:
    builders:
      auto_route_generator:auto_router_generator:
       options:
         ignore_fore_file:
           - custom_rule_1
           - custom_rule_2

Optimizing generation time

The first thing you want to do to reduce generation time, is specifying the files build_runner should process and we do that by using globs. Globs are kind of regex patterns with little differences that's used to match file names. Note: for this to work on file level you need to follow a naming convention

let's say we have the following files tree
├── lib
│ ├── none_widget_file.dart
│ ├── none_widget_file2.dart
│ └── ui
│ ├── products_screen.dart
│ ├── products_details_screen.dart

By default, the builder will process all of these files to check for a page with @RoutePage() annotation, we can help by letting it know what files we need processed, e.g only process the files inside the ui folder: Note (**) matches everything including '/';

targets:
  $default:
    builders:
      auto_route_generator:auto_route_generator:
        generate_for:
          - lib/ui/**.dart

Let's say you have widget files inside of the ui folder, but we only need to process files ending with _screen.dart

targets:
  $default:
    builders:
      auto_route_generator:auto_route_generator:
        generate_for:
          - lib/ui/**_screen.dart

Now only products_screen.dart, products_details_screen.dart will be processed

The same goes for @AutoRouterConfig builder

targets:
  $default:
    builders:
      auto_route_generator:auto_route_generator: # this for @RoutePage
        generate_for:
          - lib/ui/**_screen.dart
      auto_route_generator:auto_router_generator: # this for @AutoRouterConfig
        generate_for:
          - lib/ui/router.dart

Enabling cached builds

This is still experimental When cached builds are enabled, AutoRoute will try to prevent redundant re-builds by analyzing whether the file changes has any effect on the extracted route info, e.g any changes inside of the build method should be ignored.

Note Enable cached builds on both generators

targets:
  $default:
    builders:
      auto_route_generator:auto_route_generator: # this for @RoutePage
        options:
          enable_cached_builds: true
        generate_for:
          - lib/ui/**_screen.dart
      auto_route_generator:auto_router_generator: # this for @AutoRouterConfig
        options:
          enable_cached_builds: true
        generate_for:
          - lib/ui/router.dart

AutoLeadingButton-BackButton

AutoLeadingButton is AutoRoute's replacement to the default BackButton to handle nested or parent stack popping. To use it, simply assign it to the leading property inside of AppBar

AppBar(
  title: Text(context.topRoute.name),
  leading: AutoLeadingButton(),
)

ActiveGuardObserver

ActiveGuardObserver can notify you when a guard is being checked and what guard it is. This can be used to implement a loading indicator for example.

var isLoading = false;
void initState(){
  final guardObserver = context.router.activeGuardObserver;

  guardObserver.addListener(() {
    setState((){
      isLoading = guardObserver.guardInProgress;
    });
  });
}

Migrating to v6

In version 6.0 AutoRoute aims for less generated code for more flexibility and less generation time.

1. Instead of using MaterialAutoRouter, CupertinoAutoRouter, etc, we now only have one annotation for our router which is @AutoRouterConfig() and instead of passing our routes list to the annotation we now pass it to the overridable getter routes inside of the generated router class and for the default route type you can override defaultRouteType

Before

// @CupertinoAutoRouter
// @AdaptiveAutoRouter
// @CustomAutoRouter
@MaterialAutoRouter(
  routes: <AutoRoute>[
    // routes go here
  ],
)
class $AppRouter {}

After

@AutoRouterConfig()
class AppRouter extends $AppRouter {

 @override
 RouteType get defaultRouteType => RouteType.material(); //.cupertino, .adaptive ..etc

 @override
 List<AutoRoute> get routes => [
   // routes go here
 ];
}

2. Passing page components as types is changed, now you'd annotate the target page with @RoutePage() annotation and pass the generated result.page to AutoRoute():

Before

class ProductDetailsPage extends StatelessWidget {}
AutoRoute(page: ProductDetailsPage) // as Type

After

@RoutePage() // Add this annotation to your routable pages
class ProductDetailsPage extends StatelessWidget {}
AutoRoute(page: ProductDetailsRoute.page) // ProductDetailsRoute is generated

3. EmptyRoutePage no longer exists, instead you will now make your own empty pages by extending the AutoRouter widget

Before

AutoRoute(page: EmptyRoutePage, name: 'ProductsRouter') // as Type

After

@RoutePage(name: 'ProductsRouter')
class ProductsRouterPage extends AutoRouter {}
AutoRoute(page: ProductsRouter.page)

4. Passing route guards is also changed now, instead of passing guards as types you now pass instances.

Before

AutoRoute(page: ProfilePage, guards:[AuthGuard]) // as Type

After

AutoRoute(page: ProfilePage, guards:[AuthGuard(<params>)]) // as Instance

Examples

coming soon

Support auto_route

You can support auto_route by liking it on Pub and staring it on Github, sharing ideas on how we can enhance a certain functionality or by reporting any problems you encounter and of course buying a couple coffees will help speed up the development process

auto_route_library's People

Contributors

adar2378 avatar akhilaand avatar akvus avatar bbjay avatar f-person avatar foxanna avatar garzas avatar jonpittock avatar jorgelrj avatar jtdlab avatar kiruel avatar krokyze avatar kuhnroyal avatar leoando avatar lrsvmb avatar mernen avatar milad-akarie avatar mirland avatar nateshmbhat avatar payam-zahedi avatar radomir9720 avatar reprevise avatar ricpar11 avatar sametsahin10 avatar shemhazai avatar spencerc avatar theweiweiway avatar tinhhuynh avatar vasilich6107 avatar volskaya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

auto_route_library's Issues

use const and final variables when it is possible to

it could be nice to use const and final variables in the generated files when it is possible

  • for the better performance + reduce warnings
    so generated file will looks like:
transitionDuration: const Duration(milliseconds: n),

instead of:

transitionDuration: Duration(milliseconds: n),

What is the point of these ...Argument classes since 0.4.3?

Hey,

since introduction of these argument classes, it is really hard to just call different routes by name receiving the same type of arguments. Is there any reason for this change to the previous version? It is still only a runtime check.

Cheers, Sascha

ExtendedNavigator.of(context) do not allows to call pushSecondScreen method

Hello. I'm checking description:

ExtendedNavigator.of(context).pushSecondScreen(args...);
//or
ExtendedNavigator.ofRouter<Router>().pushSecondScreen(args...)

ExtendedNavigator.ofRouter<Router>() works as expected - I can call pushSecondScreen method. ExtendedNavigator.of(context) doesn't have any page-specific methods. Is the description correct?

Compilation Fails v0.3.0

Just updated to new v0.3.0 and can't build Flutter app anymore.

Compiler message:
../../../flutter/.pub-cache/hosted/pub.dartlang.org/auto_route-0.3.0/lib/src/extended_navigator.dart:79:56: Error: This expression has type 'void' and can't be used.
  bool pop<T extends Object>([T result]) => _navigator.pop<T>(result);

My Flutter version:

[✓] Flutter (Channel dev, v1.15.3, on Mac OS X 10.14.6 18G103, locale en-US)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 11.3.1)
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.42.1)
[✓] Connected device (1 available)

Custom callback(function) as parameter to the route failed to generate

Hi,
There is a problem when using callback to generated route. for example if we have a class like this

class MyPage extends StatelessWidget {
  const MyPage({
    Key key,
    @required this.id,
    @required this.name,
    @required this.callback,
  }) : super(key: key);

  final int id;
  final String name;
  final void Function(int id) callback;
}

the builder can not generate proper class for arguments, it failed immediately. and shows error

// Error: NoSuchMethodError: The getter 'source' was called on null.
//        Receiver: null
//        Tried calling: source

but using a typedef for the callback function fix the problem

typedef CustomCallback = void Function(int id);

Default argument value

Hi thanks for your amazing work and lib. it is possible to have default value on routes?
My use case is, I have a wrapper class that I use for all my routes, this class has pageType parameter, on each route definition, I want to pass the default value for this parameter. Thanks

Use observers

Is it possible to add observers? For example HeroController()?

CustomRoute - transitions are not generated

When I annotate a field with @CustomRoute:

  @CustomRoute(
    transitionsBuilder: TransitionsBuilders.zoomIn,
    durationInMilliseconds: 200,
  )
  ThirdPage thirdPage;

I would expect this in the generated file:

...
return PageRouteBuilder(
  transitionsBuilder: TransitionsBuilders.zoomIn,
  transitionDuration: Duration(milliseconds: 200),
  pageBuilder: (ctx, animation, secondaryAnimation) =>
      ThirdPage(userName: typedArgs.userName, points: typedArgs.points),
  settings: settings,
);
...

Instead, transitionsBuilder and transitionDuration aren't passed in as arguments to the PageRouteBuilder.

Customize Unknown Route Page

Hi there, thanks for this awesome package ❤️

Is it possible to customize the page when an unknown route is pushed? Or maybe provide the unknown page ourselves? The current unknown page is kinda scary IMHO haha.

NoSuchMethodError: The getter 'isInSystemLibrary' was called on null.

I am getting a build error (below), while trying to add a new route.

[SEVERE] auto_route_generator:autoRouteGenerator on lib/routes/router.dart:
Error running AutoRouteGenerator
NoSuchMethodError: The getter 'isInSystemLibrary' was called on null.
Receiver: null
Tried calling: isInSystemLibrary

@autoRouter
class $Router {
  @initial
  LoginPage loginPage;
  DashboardPage dashboardPage;
  DashboardDetailPage dashboardDetailPage;
}
class DashboardDetailPage extends StatelessWidget {
  // final dynamic itemPayload;
  final String selectedType;

  const DashboardDetailPage({Key key, this.selectedType}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(child: Text("HELL"));
  }
}

if I comment DashboardDetailPage, then everything works fine. I want to add a dynamic route argument in this class/page.

Add fullscreenDialog to @CustomRoute

fullscreenDialog is only available in @MaterialRoute and @CupertinoRoute but should also be supported by a custom route with PageRouteBuilder.

Make generated class linter-compatible

Hi! I'm using the linting rules specified by Flutter themselves in their repository and every time I go to debug my app, I have to click "Debug Anyways" because the generated router file throws an error. I ignored the file in my analysis_options.yaml file but the Dart linter currently has a bug where when you open ignored files, the errors and/or warnings stay. The error I'm getting is when you define a route (MaterialPageRoute, PageRoute, CupertinoPageRoute), it doesn't have a type argument such as MaterialPageRoute<dynamic>.

There's other things like not defining type annotations for the route strings (couldn't you just make them all of type String?) and the variable args not being used at the top of onGenerateRoute() but it doesn't bother me as much as the red errors. It would be great if you could just define the PageRoutes type arguments to dynamic.

am I doing it wrong ?

[INFO] Starting Build

[INFO] Updating asset graph...
[INFO] Updating asset graph completed, took 1ms

[INFO] Running build...
[INFO] Running build completed, took 7ms

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 14ms

[INFO] Succeeded after 23ms with 0 outputs (0 actions)

and it stops here . I got noting file names like Route.dart.

AutoRouteGenerator fails to generate - "Error: NoSuchMethodError: The getter 'isInSystemLibrary' was called on null."

Route generator fails to generate a routeGenerator.dart class. Instead, the routeGenerator.gr.dart holds the following comment:

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// AutoRouteGenerator
// **************************************************************************

// Error: NoSuchMethodError: The getter 'isInSystemLibrary' was called on null.
//        Receiver: null
//        Tried calling: isInSystemLibrary

my $RouteGenerator class looks like this:

@AutoRouter(generateRouteList: true)
class $RouteGenerator {
  @initial
  LoadScreen loadScreen;
  @CustomRoute(transitionsBuilder: TransitionsBuilders.fadeIn, durationInMilliseconds: 0, )
  Application application;
  SelectedCatagoryPage catagory;
  SelectedRewardPage reward;
  BusinessPage business;
  @CustomRoute(transitionsBuilder: TransitionsBuilders.fadeIn, durationInMilliseconds: 0)
  LoginPage login;
  @CustomRoute(transitionsBuilder: TransitionsBuilders.fadeIn, durationInMilliseconds: 0)
  OnBoard onBoard;
}

There are no broken imports, and no errors in my $RouteGenerator class.

Error - compilation never finalises.

Hi Milad, first of all thanks for an awesome Plugin.

I'm having an issue with my generated routes causing some sort of flutter error. My flutter program is never fully compiled (i.e Android Studio doesn't show the hot reload button). The UI actually works and you can navigate around.

Occurring for Android API 28 and 29.

I have no idea if this is an issue with the emulator, Flutter, Android Studio or the plugin generated code.

I generated my code based on Reso's tutorial:
https://resocoder.com/2020/01/10/flutter-zero-boilerplate-router-with-auto-route-flutter-navigation-tutorial/

But i get stuck with the following message:

D/FlutterView( 6866): Attaching to a FlutterEngine: io.flutter.embedding.engine.FlutterEngine@e184f11
D/FlutterActivityAndFragmentDelegate( 6866): Executing Dart entrypoint: main, and sending initial route: /
This is taking longer than expected...

Code:
Main.dart

import 'package:fhir_test/bloc/bloc.dart';
import 'package:fhir_test/route/router.gr.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<PatientBloc>(
          create: (BuildContext context) => PatientBloc(),
        ),
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        initialRoute: Router.homePage,
        onGenerateRoute: Router.onGenerateRoute,
        navigatorKey: Router.navigatorKey,
      ),
    );
  }
}

Router

import 'package:auto_route/auto_route_annotations.dart';
import 'package:fhir_test/ui/homePage.dart';
import 'package:fhir_test/ui/patientSearchPage.dart';

@autoRouter
class $Router {
  @initial
  HomePage homePage;
  PatientSearchPage patientSearchPage;
}

Routes

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:auto_route/router_utils.dart';
import 'package:fhir_test/ui/homePage.dart';
import 'package:fhir_test/ui/patientSearchPage.dart';

class Router {
  static const homePage = '/';
  static const patientSearchPage = '/patientSearchPage';
  static GlobalKey<NavigatorState> get navigatorKey =>
      getNavigatorKey<Router>();
  static NavigatorState get navigator => navigatorKey.currentState;

  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
    final args = settings.arguments;
    switch (settings.name) {
      case Router.homePage:
        return MaterialPageRoute(
          builder: (_) => HomePage(),
          settings: settings,
        );
      case Router.patientSearchPage:
        if (hasInvalidArgs<PatientSearchPageArguments>(args)) {
          return misTypedArgsRoute<PatientSearchPageArguments>(args);
        }
        final typedArgs =
            args as PatientSearchPageArguments ?? PatientSearchPageArguments();
        return MaterialPageRoute(
          builder: (_) =>
              PatientSearchPage(key: typedArgs.key, title: typedArgs.title),
          settings: settings,
        );
      default:
        return unknownRoutePage(settings.name);
    }
  }
}

//**************************************************************************
// Arguments holder classes
//***************************************************************************

//PatientSearchPage arguments holder class
class PatientSearchPageArguments {
  final Key key;
  final String title;
  PatientSearchPageArguments({this.key, this.title});
}

Flutter Doctor:

[√] Flutter (Channel dev, v1.14.1, on Microsoft Windows [Version 10.0.17134.1130], locale en-AU)
• Flutter version 1.14.1 at C:\flutter
• Framework revision c88320458e (32 hours ago), 2020-01-15 11:38:02 -0500
• Engine revision bc41ab5139
• Dart version 2.8.0 (build 2.8.0-dev.1.0 fe666ce592)

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at C:/Users/pullend/AppData/Local/Android/Sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• ANDROID_HOME = C:/Users/pullend/AppData/Local/Android/Sdk
• Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
• All Android licenses accepted.

[√] Chrome - develop for the web
• Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Android Studio (version 3.5)
• Android Studio at C:\Program Files\Android\Android Studio
• Flutter plugin version 42.1.1
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)

[√] VS Code (version 1.41.1)
• VS Code at C:\Users\pullend\AppData\Local\Programs\Microsoft VS Code
• Flutter extension version 3.7.1

[!] Proxy Configuration
• HTTP_PROXY is set
! NO_PROXY is not set

[√] Connected device (3 available)
• AOSP on IA Emulator • emulator-5554 • android-x86 • Android 9 (API 28) (emulator)
• Chrome • chrome • web-javascript • Google Chrome 79.0.3945.117
• Web Server • web-server • web-javascript • Flutter Tools

! Doctor found issues in 1 category.

Can't run the `/example` : auto_route_generator from path is forbidden

flutter packages pub run build_runner build

No .dart_tool/package_config.json file found, please run "pub get" first. Starting with Dart 2.7 this file configures the resolution of package import URIs. Running "pub get" will generate this file.

pub get

Resolving dependencies...
Because every version of auto_route_generator from path depends on auto_route from hosted and
example depends on auto_route from path, auto_route_generator from path is forbidden.
So, because example depends on auto_route_generator from path, version solving failed.

ExtendedNavigator error

Hi,

When I try to use

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    // Tell MaterialApp to use our ExtendedNavigator instead of
    // the native one by assigning it to it's builder
    // instead of return the nativeNavigator we're returning our ExtendedNavigator
     builder: ExtendedNavigator<Router>(router: Router()),
    );
  }
}

I'm getting errors:
The argument type 'ExtendedNavigator' can't be assigned to the parameter type 'Widget Function(BuildContext, Widget)'.dart(argument_type_not_assignable) The type 'ExtendedNavigator' is declared with 0 type parameters, but 1 type arguments were given. Try adjusting the number of type arguments.dart(new_with_invalid_type_parameters)

on the other hand this is working fine:

return MaterialApp(
      title: 'Covid Checker',
      debugShowCheckedModeBanner: false,
      initialRoute: Router.formPageView,
      onGenerateRoute: Router.onGenerateRoute,
      navigatorKey: Router.navigator.key,
      theme: covidTheme(),
    );

What am I doing wrong?

And the other question is how to add a nested navigator? Should it also be passed at the top level of the widget tree (MaterialApp)?

Question : How to use with navkey ?

Hi,

I have trouble using this package with navKey. Do you have an example by any chance ?

I have declared :
static final navKey = new GlobalKey<ExtendedNavigatorState<Router>>();
but when I access navKey.currentStateis always null.

Regards,

Rename router.dart to router.g.dart

Maybe this can be renamed, it seems to be a convention for generated files.

Currently one has to manually add router.dart to .gitignore where as router.g.dart would often be ignored by default.
In my case I have to also change build files in CI because router.dart doesn't match the pattern.

Maybe even allow changing the file name and path by adding an @AutoRouter() annotation or something like this.

/// /lib/foo/bar_router.dart
import 'package:auto_route/auto_route_annotation.dart';

part 'bar_router.g.dart';

@AutoRouter()
class BarRouter with _BarRouter {
}

Jerky transition animation iOS

Hi,

Sorry for bothering you again ;) Up to this point I worked on the simulator and didn't care that the transition animation looked bad. Today I started to use real devices iPhone 5s, iPhone X and iPhone Xs Max. It looks bad on all of them. Instead of being smooth, it is very jerky. In addition to it when displaying the system alert it freezes the transition in the middle (a picture attached). I haven't touched anything related to transitions so it has to be a default push one. Any ideas how I can fix it? BTW before using the Router,

I had the Pave View and it was working very smoothly. Cheers,
mat

Accessing the navigator stack

Your library generates classes encapsulating arguments for a page which is great. My question is how could I access a stack so that when I do pop I can get what arguments have been passed to a previous page. In my case, all pages are of the same type and the only difference is something like a view model which one of argument injected into them.

Using ExtendedNavigator to navigate without context causes root navigator to be null when enabling Widget Select Mode in Flutter Inspector

Exception:

E/flutter (19356): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The method 'pushReplacementNamed' was called on null.
E/flutter (19356): Receiver: null
E/flutter (19356): Tried calling: pushReplacementNamed<Object, Object>("/second-screen-route")
E/flutter (19356): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (19356): #1      MyHomePage.build.<anonymous closure> (package:flutterapp/main.dart:29:12)
E/flutter (19356): #2      _rootRunUnary (dart:async/zone.dart:1134:38)
E/flutter (19356): #3      _CustomZone.runUnary (dart:async/zone.dart:1031:19)
E/flutter (19356): #4      _FutureListener.handleValue (dart:async/future_impl.dart:140:18)
E/flutter (19356): #5      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:682:45)
E/flutter (19356): #6      Future._propagateToListeners (dart:async/future_impl.dart:711:32)
E/flutter (19356): #7      Future._complete (dart:async/future_impl.dart:516:7)
E/flutter (19356): #8      new Future.delayed.<anonymous closure> (dart:async/future.dart:313:16)
E/flutter (19356): #9      _rootRun (dart:async/zone.dart:1122:38)
E/flutter (19356): #10     _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (19356): #11     _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (19356): #12     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:965:23)
E/flutter (19356): #13     _rootRun (dart:async/zone.dart:1126:13)
E/flutter (19356): #14     _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (19356): #15     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:949:23)
E/flutter (19356): #16     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:23:15)
E/flutter (19356): #17     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
E/flutter (19356): #18     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
E/flutter (19356): #19     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Minimal repro:

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutterapp/router.gr.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: ExtendedNavigator<Router>(router: Router()),
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Future.delayed(Duration(seconds: 1)).then((value) {
      ExtendedNavigator.rootNavigator
          .pushReplacementNamed(Routes.secondScreenRoute);
    });
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('First page'),
          ],
        ),
      ),
    );
  }
}

class MySecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Second page'),
          ],
        ),
      ),
    );
  }
}
import 'package:auto_route/auto_route_annotations.dart';
import 'package:flutterapp/main.dart';

@MaterialAutoRouter()
class $Router {
  @initial
  MyHomePage myHomePage; // your desired route name

  MySecondPage secondScreenRoute;
}

Pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  # Simple routing
  auto_route: ^0.4.2


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.3

dev_dependencies:
  flutter_test:
    sdk: flutter

  # Auto Route library for zero boilerplate navigation
  auto_route_generator: ^0.4.0
  build_runner:

Flutter doctor:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v1.15.17, on Mac OS X 10.15.3 19D76, locale en-AE)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 11.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 3.6)
[✓] Connected device (3 available)

Thanks a lot for you work on this magical library by the way!

isInitialRoute not defined for RouteSettings

I'm compiling my application for the web and I get the following error:

Target dart2js failed: Exception: /C:/flutter/.pub-cache/hosted/pub.dartlang.org/auto_route-0.4.0/lib/src/extended_navigator.dart:89:24:
Error: The getter 'isInitialRoute' isn't defined for the class 'RouteSettings'.
 - 'RouteSettings' is from 'package:flutter/src/widgets/navigator.dart' ('/C:/flutter/packages/flutter/lib/src/widgets/navigator.dart').
    if (route.settings.isInitialRoute &&
                       ^^^^^^^^^^^^^^
Error: Compilation failed.

I've rebuild the router.gr.dart file and still the issue persists.

3rd party imports use the "src" directory

Hello Milad!
The same problem that happened in injectable mentioned in the issue no. 12 also happens in auto_route with route arguments.

  final Option<Note> editedNoteOption;

  const NoteFormPage({
    Key key,
    @required this.editedNoteOption,
  }) : super(key: key);

generates the following erroneous import

import 'package:dartz/src/option.dart';

Middleware

How to write middleware to check login status for each route

Introduce optional re-generating of specific routes

Thanks for this awesome package to simplify routing.
In my use case, trying to provide a bloc to my route page seem impossible since the route file is auto regenerated on build_runner watch thus clearing my changes made to the route.gr.dart file. My suggestion would be to annotate a specific route with @noAutoGenerate or something related to disable the route from re-generating.

thanks.

How to auto redirect to login page with guards?

In my HomeScreen's initState I'm checking for Router.navigator.canNavigate(Router.secureScreen). If true it'll redirect there, if false it'll redirect to the login page.

Is there any way this (redirecting to login if canNavigate is false) can be configured and done automatically for every page with a guard?

Typedef Function generated as null

I have this widget and I'm using ValueChanged from flutter

class AddressScreen extends StatefulWidget {
  final ValueChanged<UserAddress> onAddressSelected;
  final bool showCloseAsCross;

  const AddressScreen(
      {Key key, this.showCloseAsCross = false, this.onAddressSelected})
      : super(key: key);
  @override
  AddressScreenState createState() => AddressScreenState();
}

and this is the generated argument


//**************************************************************************
// Arguments holder classes
//***************************************************************************

//AddressScreen arguments holder class
class AddressScreenArguments{
final Key key;
final bool showCloseAsCross;
final null onAddressSelected;
AddressScreenArguments({
this.key,this.showCloseAsCross = false,this.onAddressSelected});
}

I'm using auto_router version 0.3.0

Being able to list all routes

I have a drawer and I'm navigating to the pages by index. It would be great if in the generated class there was a list of all the routes so that I can just say for example Router.routes[index]. I know that I could just maintain a list of all the routes myself but it would be cumbersome to maintain it over time and if I, say, add new routes and forget to add it to said list, my code would break. It would also avoid me having to create a function to get a route from the index. The list should be in the order that they are defined in the class.

Android system back button closes whole app

Hello,

After upgrading library version from 0.2.2 to the newest one (0.4.1) I encountered an issue with Android system back button. Whenever I press it, even if I am not on initial route, application is being closed. If I call pop() method on my router, then it works in expected way. When I swipe back on iPhone, it also works.

I tried to go around it and add WillPopScope widget to my main page, but onWillPop was never called.

My MaterialApp:

MaterialApp(
        title: translate(Keys.Common_App_Name),
        localizationsDelegates: [
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
          localizationDelegate
        ],
        supportedLocales: localizationDelegate.supportedLocales,
        locale: localizationDelegate.currentLocale,
        theme: Themes.mainTheme(context),
        builder: ExtendedNavigator<Router>(router: Router()),
      )

Using streams as parameter for route

Hi,
we have an issue if we are using stream as a parameter of a route. We got this error:

Compiler message: lib/routes/router.gr.dart:12:8: Error: Not found: 'dart:async/stream.dart' import 'dart:async/stream.dart';

Is there a possibility to use streams as parameters?

Thanks,
Werner

Pressing back button pop nested navigator

Hi,

Pressing system back button inside nested navigator cause navigate to main navigator instead of returning ot prevoius page in nested navigator.

Main Navigator -> Main Page -> Nested Navigator -> Page A -> Page B

Pressing back button on Page B should return to Page A

error: "Directives must appear before any declarations" when using nested navigator

Hi there,

First, thanks for the awesome package!

I'm trying to use a nested navigator and I keep getting the following error:

Directives must appear before any declarations

When looking at router.gr.dart, I'm seeing that imports are being duplicated for each declared router.

Here's my router.dart file:

import 'package:animations/animations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:auto_route/auto_route_annotations.dart';
import 'package:flutter/material.dart';
import 'package:scout_app/locator.dart';
import 'package:scout_app/router.gr.dart';
import 'package:scout_app/screens/secure/customer_edit_screen.dart';
import 'package:scout_app/screens/secure/customer_new_screen.dart';
import 'package:scout_app/screens/secure/customer_profile_screen.dart';
import 'package:scout_app/screens/secure/index_screen.dart';
import 'package:scout_app/screens/forgot_password_screen.dart';
import 'package:scout_app/screens/login_screen.dart';
import 'package:scout_app/screens/secure/staff_edit_screen.dart';
import 'package:scout_app/screens/secure/staff_new_screen.dart';
import 'package:scout_app/screens/secure/staff_profile_screen.dart';
import 'package:scout_app/services/session_service.dart';

@MaterialAutoRouter()
class $Router {
  @CustomRoute(transitionsBuilder: fadeThroughTransition)
  LoginScreen loginScreen;

  @MaterialRoute(fullscreenDialog: true)
  ForgotPasswordScreen forgotPasswordScreen;

  @GuardedBy([AuthGuard])
  @CustomRoute(initial: true, transitionsBuilder: fadeThroughTransition)
  SecureScreen secureScreen;
}

@MaterialAutoRouter()
class $CustomerRouter {
  @initial
  CustomerProfileScreen customerProfileScreen;

  CustomerNewScreen customerNewScreen;

  @CustomRoute(transitionsBuilder: sharedAxisTransition)
  CustomerEditScreen customerEditScreen;
}

@MaterialAutoRouter()
class $StaffRouter {
  @initial
  StaffNewScreen staffNewScreen;

  StaffProfileScreen staffProfileScreen;

  @CustomRoute(transitionsBuilder: sharedAxisTransition)
  StaffEditScreen staffEditScreen;
}

And here's the output in router.gr.dart:

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// AutoRouteGenerator
// **************************************************************************

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:auto_route/auto_route.dart';
import 'package:scout_app/screens/login_screen.dart';
import 'package:scout_app/router.dart';
import 'package:scout_app/screens/forgot_password_screen.dart';
import 'package:scout_app/screens/secure/index_screen.dart';
abstract class Routes{
static const loginScreen = '/login-screen';
static const forgotPasswordScreen = '/forgot-password-screen';
static const secureScreen = '/';
}

class Router extends RouterBase {
@override
Map<String, List<Type>> get guardedRoutes => {
Routes.secureScreen:[AuthGuard],};


 //This will probably be removed in future versions
  //you should call ExtendedNavigator.ofRouter<Router>() directly
    static ExtendedNavigatorState get navigator =>
      ExtendedNavigator.ofRouter<Router>();
      

@override
Route<dynamic> onGenerateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case Routes.loginScreen:
return PageRouteBuilder<dynamic>(pageBuilder: (ctx, animation, secondaryAnimation) => LoginScreen(), settings: settings,transitionsBuilder: fadeThroughTransition,);
case Routes.forgotPasswordScreen:
return MaterialPageRoute<dynamic>(builder: (_) => ForgotPasswordScreen(), settings: settings,fullscreenDialog:true,);
case Routes.secureScreen:
return PageRouteBuilder<dynamic>(pageBuilder: (ctx, animation, secondaryAnimation) => SecureScreen(), settings: settings,transitionsBuilder: fadeThroughTransition,);
default: return unknownRoutePage(settings.name);
}

}
}

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:auto_route/auto_route.dart';
import 'package:scout_app/screens/secure/customer_profile_screen.dart';
import 'package:scout_app/screens/secure/customer_new_screen.dart';
import 'package:scout_app/screens/secure/customer_edit_screen.dart';
import 'package:scout_app/router.dart';
import 'package:scout_app/models/customer.dart';
abstract class Routes{
static const customerProfileScreen = '/';
static const customerNewScreen = '/customer-new-screen';
static const customerEditScreen = '/customer-edit-screen';
}

class CustomerRouter extends RouterBase {



 //This will probably be removed in future versions
  //you should call ExtendedNavigator.ofRouter<Router>() directly
    static ExtendedNavigatorState get navigator =>
      ExtendedNavigator.ofRouter<CustomerRouter>();
      

@override
Route<dynamic> onGenerateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case Routes.customerProfileScreen:
if(hasInvalidArgs<String>(args
)){return misTypedArgsRoute<String>(args);}
final typedArgs = args as String
;return MaterialPageRoute<dynamic>(builder: (_) => CustomerProfileScreen(typedArgs), settings: settings,);
case Routes.customerNewScreen:
return MaterialPageRoute<dynamic>(builder: (_) => CustomerNewScreen(), settings: settings,);
case Routes.customerEditScreen:
if(hasInvalidArgs<Customer>(args
)){return misTypedArgsRoute<Customer>(args);}
final typedArgs = args as Customer
;return PageRouteBuilder<dynamic>(pageBuilder: (ctx, animation, secondaryAnimation) => CustomerEditScreen(typedArgs), settings: settings,transitionsBuilder: sharedAxisTransition,);
default: return unknownRoutePage(settings.name);
}

}
}

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:auto_route/auto_route.dart';
import 'package:scout_app/screens/secure/staff_new_screen.dart';
import 'package:scout_app/screens/secure/staff_profile_screen.dart';
import 'package:scout_app/screens/secure/staff_edit_screen.dart';
import 'package:scout_app/router.dart';
import 'package:scout_app/models/staff.dart';
abstract class Routes{
static const staffNewScreen = '/';
static const staffProfileScreen = '/staff-profile-screen';
static const staffEditScreen = '/staff-edit-screen';
}

class StaffRouter extends RouterBase {



 //This will probably be removed in future versions
  //you should call ExtendedNavigator.ofRouter<Router>() directly
    static ExtendedNavigatorState get navigator =>
      ExtendedNavigator.ofRouter<StaffRouter>();
      

@override
Route<dynamic> onGenerateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case Routes.staffNewScreen:
return MaterialPageRoute<dynamic>(builder: (_) => StaffNewScreen(), settings: settings,);
case Routes.staffProfileScreen:
if(hasInvalidArgs<String>(args
)){return misTypedArgsRoute<String>(args);}
final typedArgs = args as String
;return MaterialPageRoute<dynamic>(builder: (_) => StaffProfileScreen(typedArgs), settings: settings,);
case Routes.staffEditScreen:
if(hasInvalidArgs<Staff>(args
)){return misTypedArgsRoute<Staff>(args);}
final typedArgs = args as Staff
;return PageRouteBuilder<dynamic>(pageBuilder: (ctx, animation, secondaryAnimation) => StaffEditScreen(typedArgs), settings: settings,transitionsBuilder: sharedAxisTransition,);
default: return unknownRoutePage(settings.name);
}

}
}

I'm using version 0.4.3.

Thanks in advance!

Is there a way to render a common widget across the App?

Hi,

With respect to your latest changes to auto_route, I am looking for a way to render a widget that can be accessible across the app. Earlier I used to something like this:

MaterialApp(
  builder: (BuildContext context, Widget widget) {
    setErrorBuilder();

    return Column(
      children: <Widget>[
        Expanded(
          child: widget,
        ),
        AlertsScreen(),
      ],
    );
  },
  title: 'App name',
  onGenerateRoute: Router.onGenerateRoute,
  initialRoute: Router.splashScreen,
  navigatorKey: Router.navigator.key,
),
  1. But now since the builder is used for Navigation I am unable to mount AlertsScreen widget into the app. Is there a way to mount a common widget which can be accessible across the app?

  2. Is it possible to get the right context for such common widgets as the below code used to throw the error:

code:

//AlertsScreen.dart

Flushbar(
      title: 'title',
      message: 'body',
      duration: const Duration(seconds: 5),
    ).show(context);

error:

[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator. The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.

AlertsScreen.dart source

Thanks

How to use with CupertinoTabView?

I was using version 0.2.2 of this package, with the following setup

  final Map<TabItem, WidgetBuilder> widgetBuilders;
  final Map<TabItem, GlobalKey<NavigatorState>> navigatorKeys;

  // in build method:
        return CupertinoTabView(
          navigatorKey: navigatorKeys[item],
          builder: (context) =>  widgetBuilders[item](context),
          onGenerateRoute: CupertinoTabViewRouter.onGenerateRoute,
        );

// where
@CupertinoAutoRouter()
class $CupertinoTabViewRouter {
  @CupertinoRoute(fullscreenDialog: false)
  JobEntriesPage jobEntriesPage;
}

However, in recent versions, the generatedCupertinoTabViewRouter.onGenerateRoute is no longer a static method.

So, how should I use the new API?
I've seen that the documentation shows some example code with MaterialApps(builder: ExtendedNavigator<Router>(router: Router())).

But this doesn't work in my case as I have multiple CupertinoTabViews, each with its own widgetBuilder.

If the solution is indeed to use ExtendedNavigator, how can I adapt it to work with multiple tabs?

How to customize route name?

Thanks for this awesome package.
Is there is anyway to customize route name instead of default variable name?

RangeError when running build

Hello!
Even a simple widget like this:

import 'package:auto_route/auto_route_annotation.dart';
import 'package:flutter/material.dart';

@AutoRoute()
class PolicySelectorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Causes a build error:

[SEVERE] auto_route_generator:autoRouteGenerator on lib/features/policies/ui/pages/policy_selector_page.dart:
Error running AutoRouteGenerator
RangeError: Value not in range: -1

I am running Flutter 1.9.1+hotfix.6 and package versions 0.111 for auto_route, 0.1.2 for auto_route_generator.

Why Arguments class are not generated for single parameter Widget?

I'm new to auto_route and was looking for about 5 minutes for generated Arguments class with no success. After that I found that:

if you define more then one parameter in your screen constructor autoRoute will automatically generate a class that holds your screen arguments and keep them typed

Even though a single argument Widget is pretty simple it still makes sense to use Arguments class instead of passing argument as is.

Please remove this limitation and generate Arguments class for routes with single param

Could the generated class not use static members?

Hey Milad,

after enjoying your injectable package, it would be really nice to also use the generated auto_route class with DI (i.e. for mocking, etc.). What about generating a non-static class? If anybody really, really wants the static variant, there is always a possibility of a singleton.

Cheers, Sascha

Remove @InitialRoute or make it optional

There is nothing special about the initial route compared to other routes.
It should be possible to make this a material/cupertino/custom route so that a back navigation will use the correct animation.

Currently it always is a MaterialPageRoute and it is required. In order to circumvent this, I always create a dummy route with @InitialRoute() but don't use it.

Compile error on v0.3.0

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel dev, v1.15.3, on Microsoft Windows [Version 10.0.17763.437], locale zh-TW)
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] IntelliJ IDEA Community Edition (version 2019.3)
@MaterialAutoRouter()
class $ScreenRouter {
  @initial
  FeatureScreen2 featureScreen;
}
class FeatureScreen2 extends StatefulWidget {
  @override
  _FeatureScreen2State createState() => _FeatureScreen2State();
}

class _FeatureScreen2State extends State<FeatureScreen2> {
  ...
  @override
  Widget build(BuildContext context) {
    MyLogger.info(msg: 'bar build', tag: tag);
    return Scaffold(
      key: _scaffoldKey,
      bottomNavigationBar: _navBar,
      body: Container(),
    );
  }
  ...
}
[+5192 ms] Compiler message:
[        ] ../../flutter/.pub-cache/hosted/pub.dartlang.org/auto_route-0.3.0/lib/src/extended_navigator.dart:79:56: Error: This expression has type 'void' and can't be used.
[        ]   bool pop<T extends Object>([T result]) => _navigator.pop<T>(result);
[        ]                                                        ^
[+2399 ms] Target kernel_snapshot failed: Exception: Errors during snapshot creation: null
[        ] build failed.
[        ] FAILURE: Build failed with an exception.
[        ] * Where:
[        ] Script 'C:\Dev\flutter\packages\flutter_tools\gradle\flutter.gradle' line: 817
[        ] * What went wrong:
[        ] Execution failed for task ':app:compileFlutterBuildDebug'.
[        ] > Process 'command 'C:\Dev\flutter\bin\flutter.bat'' finished with non-zero exit value 1
[        ] * Try:
[        ] Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
[        ] * Get more help at https://help.gradle.org
[        ] BUILD FAILED in 8s

This error happened after upgrading to v0.3.0
There's no compile error on the last version (v0.2.2)
Does the API has any version requirement?

Issue with remaining handmade arguments

You got my star on a very nice package there 👍.

Since I move from my handmade routing to auto_route, I encounter a bug or at least something to specify in the documentation.

If you have you personal route argument before the class, the generator will not match the class but rather the first class. So if you put the class argument first, it will generate a bad file.

class WidgetsListViewArguments {
  final String title;

  WidgetsListViewArguments({this.dataFuture, this.title});
}

class MainClass extends StatelessWidget {
  const MainClass({Key key, this.title})
      : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    ...
}

Should at least mention to either suppress them or put them after the class.

Hope this is clear.

Regards.

Question: best approach to protect screens behind auth

[This is more of a question than an issue]

Like a lot of apps, I've got a situation where a user can use a lot of the app without being logged in, but it gets to a point where they need to be logged in to continue to the next screen.

Imagine the user is on screen /a and tries to navigate to screen /b. If they're already logged in, pushing to /b should go straight there, otherwise, they get taken via /login which, after successful authentication, then sends them on to b.

I guess it is sort of like the concept of the route guard, but with a bit more logic that allows a passthrough transition.

Any thoughts on whether there's some existing behaviour in auto_route that might help achieve this? Or any other pointers/suggestions? Thanks.

How to implement a Nested Navigation?

Hi there!

Very cool packages, can save a lot of time!
I have one question: Is it already possible or planned to integrate Nested Navigation with your Package?
So that we have a NestedRouter Class or so.

Best regards
Andy

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.