• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Milad-Akarie/auto_route_library: Flutter route generator

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

Milad-Akarie/auto_route_library

开源软件地址(OpenSource Url):

https://github.com/Milad-Akarie/auto_route_library

开源编程语言(OpenSource Language):

Dart 99.6%

开源软件介绍(OpenSource Introduction):

MIT License stars pub version Discord Badge

Buy Me A Coffee

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

Create a placeholder class and annotate it with @MaterialAutoRouter which takes a list of routes as a required argument.

Note: Unless you want to generate a part of file (.gr.dart) The name of the router must be prefixed with $ so we have a generated class with the same name minus the $.

              
// @CupertinoAutoRouter              
// @AdaptiveAutoRouter              
// @CustomAutoRouter              
@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(page: BookListPage, initial: true),              
    AutoRoute(page: BookDetailsPage),              
  ],              
)              
class $AppRouter {}              

Using part builder ( New in version 3.0.0+)

To generate a part-of file instead of a stand alone AppRouter class, simply add a Part Directive to your AppRouter and extend the generated private router.

part 'app_router.gr.dart';      
        
@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(page: BookListPage, initial: true),              
    AutoRoute(page: BookDetailsPage),              
  ],              
)              
// extend the generated private router        
class AppRouter extends _$AppRouter{}              

Tip: You can Shorten auto-generated route names from e.g. BookListPageRoute to BookListRoute using the replaceInRouteName argument.

Now simply run the generator

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

flutter packages pub run build_runner watch              

if you want the generator to run one time and exits use

flutter packages pub run build_runner build              

Finalize the setup

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

// assuing 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(      
      routerDelegate: _appRouter.delegate(),      
      routeInformationParser: _appRouter.defaultRouteParser(),      
    );      
  }      
}      

Generated Routes

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

class BookListRoute extends PageRouteInfo {              
  const BookListRoute() : super(name, path: '/books');              
              
  static const String name = 'BookListRoute';              
}              

if the declared route has children AutoRoute will add a children parameter to its constructor to be used in nested navigation. more on that here.

class UserRoute extends PageRouteInfo {              
   UserRoute({List<PagerouteInfo> children}) :              
    super(              
         name,               
         path: '/user/:id',              
         initialChildren: children);              
  static const String name = 'UserRoute';              
}              

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 using paths            
router.pushNamed('/books')             
          
// removes last entry in stack and pushs provided route           
// if last entry == provided route page will just be updated          
router.replace(const BooksListRoute())              
// or by using 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 using paths            
router.navigateNamed('/books')          
        
// on Web it calls window.history.back();      
// on Native it navigates you back       
// to the previous location      
router.navigateBack();      
      
// 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          
// entires might just update if already exist          
router.replaceAll([          
   LoginRoute()          
]);          
// pops the last page unless stack has 1 entry              
context.router.pop();             
// keeps poping routes until predicate is satisfied          
context.router.popUntil((route) => route.name == 'HomeRoute');          
// a simplifed version of the above line          
context.router.popUntilRouteWithName('HomeRoute');          
// 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 satisfis the predicate          
// this works exactly like removing items from a regualar 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.navigateBack();         
context.popRoute();          

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.

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 it's corresponding page.

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

Note: all args 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.pop(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.

AutoRoute<bool>(page: LoginPage),           

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.pop<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:

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 it's corresponding page.

context.router.push(              
      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.router.pop();              

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.

@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(              
      path: '/dashboard',              
      page: DashboardPage,              
      children: [              
        AutoRoute(path: 'users', page: UsersPage),              
        AutoRoute(path: 'posts', page: PostsPage),          
        AutoRoute(path: 'settings', page: SettingsPage),                
      ],              
    ),          
    AutoRoute(path: '/login', page: LoginPage)          
  ],              
)              
class $AppRouter {}              

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(),          
        )          
      ],          
    );          
  }          
}          

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 '' or set it as initial.

   AutoRoute(              
      path: '/dashboard',              
      page: DashboardPage,              
      children: [              
        AutoRoute(path: '', page: UsersPage),          
        //The same thing can be done using the initial flag          
        //AutoRoute(page: UsersPage,initial: true),              
        AutoRoute(path: 'posts', page: PostsPage),              
      ],              
    ),              

or by using a RedirectRoute

   AutoRoute(              
   path: '/dashboard',              
   page: DashboardPage,              
      children: [              
         RedirectRoute(path: '', redirectTo: 'users'),              
         AutoRoute(path: 'users', page: UsersPage),              
         AutoRoute(path: 'posts', page: PostsPage),               
      ],              
   ),        

which can be simplified to the following where auto_route generates the redirect code for you.

   AutoRoute(              
      path: '/dashboard',              
      page: DashboardPage,              
      children: [              
        // RedirectRoute(path: '', redirectTo: 'users'),              
        AutoRoute(path: 'users', page: UsersPage, initial: true),              
        AutoRoute(path: 'posts', page: PostsPage),               
      ],              
    ),              

Things to keep in mind when implementing nested navigation

1- Each router manages it's 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 finally it 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 tree 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 declaraed as children          
    // routes of /dashboard           
      routes: const [          
        UsersRoute(),          
        PostsRoute(),          
        SettingsRoute(),          
      ],          
      builder: (context, child, animation) {          
        // 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          
        //           
        //alterntivly you could use a global key          
        return Scaffold(          
            body: FadeTransition(          
              opacity: animation,          
              // the passed child is techinaclly our animated selected-tab page          
              child: 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(context) {            
 @override          
  Widget build(context) {          
    return AutoTabsScaffold(          
       routes: const [          
        UsersRoute(),          
        PostsRoute(),          
        SettingsRoute(),          
      ],          
      bottomNavigationBuilder: (_,tabsRouter) {          
          return BottomNavigationBar(          
              currentIndex: tabsRouter.activeIndex,          
              onTap: tabsRouter.setActiveIndex          
              items: [          
                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, _) {      
        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',...),          
                    ],          
   

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap