Flutter guia auto_route
Conteudo
Por que Rota Automática?
Quando se trata de criar um aplicativo com uma configuração de roteamento robusta, lidar com links diretos, navegação aninhada, rotas protegidas e outros recursos avançados de roteamento pode rapidamente se tornar uma tarefa tediosa e demorada.
O AutoRoute simplifica o processo de manipulação de roteamento e navegação em aplicativos Flutter.
Neste artigo, exploraremos o poder e a versatilidade do AutoRoute, demonstrando como ele simplifica o roteamento em seus aplicativos Flutter.
Iniciar
Para começar, adicione auto_route ao seu pubspec.yaml. Também precisamos adicionar auto_route_generator e build_runner para gerar nossas rotas.
dependencies: auto_route: ^7.4.0 dev_dependencies: auto_route_generator: ^7.1.1 build_runner: ^2.4.4
Lembre-se de sempre verificar a versão mais recente
Configuração de rota
Vamos adicionar a configuração do GoRouter ao seu aplicativo:
- Crie uma classe de roteador e anote-a com
@AutoRouterConfig
, em seguida, estenda “_$YourClassName” - Substitua o routes getter
import 'package:auto_route/auto_route.dart'; part 'app_router.gr.dart'; @AutoRouterConfig() class AppRouter extends _$AppRouter { @override List<AutoRoute> get routes => [ // add your routes here ]; }
Agora basta executar build_runner para gerar nossa rota, execute um dos comandos abaixo (recomendo o primeiro)👇🏿
// para observar as edições do sistema dos arquivos e reconstruí-los conforme necessário dart run build_runner watch // para executar build_runner apenas uma vez dart run build_runner build
Depois de executar o gerador, sua classe de roteador será gerada
Agora que temos a configuração do roteador, vamos conectá-lo ao MaterialApp
class MyApp extends StatelessWidget { MyApp({super.key}); /// create an instance of `AppRouter` final _appRouter = AppRouter(); @override Widget build(BuildContext context) { return MaterialApp.router( // hook up router to MaterialApp routerConfig: _appRouter.config(), ); } }
Agora que temos nossa configuração básica, é hora de adicionar novas rotas. Como fazer isso ??? 🙃
- escolha qual página você deseja rotear
- anote-o com
@RoutePage
e pronto, auto_router vai gerar uma nova rota para você com base na configuração do Widget, por exemplo, parâmetros do Widget
import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; @RoutePage() class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const Placeholder(); } }
Você precisará executar o gerador novamente para que o go_router possa gerar esta rota para você (somente se você não executou com watch flag).
Observe que suas rotas geradas automaticamente serão incluídas no arquivo app_router.gr criado anteriormente
Adicione a rota gerada à sua lista de rotas
AutoRouterConfig() class AppRouter extends _$AppRouter { @override List<AutoRoute> get routes => [ //HomeScreen is generated as HomeRoute because //of the replaceInRouteName property AutoRoute(page: HomeRoute.page), ]; }
Se quisermos fazer esta rota como nossa rota inicial, basta adicionar initial=true
no construtor AutoRoute
.
List<AutoRoute> get routes => [ // HomeRoute is now the initial AutoRoute(page: HomeRoute.page, initial: true), ];
Agora temos nossa configuração básica de rota!
Navegação entre telas
Existem muitas maneiras de navegar entre destinos com auto_route
.
Para navegar para uma nova tela, chame context.router.push
// navigate by DetailsPage context.router.push(const DetailsRoute());
também podemos navegar por caminho, basta chamar context.router.pushNamed
// você pode definir caminhos ao adicionar sua rota à lista de rotas // por exemplo, AutoRoute(path:'/home', page: HomeRoute.page) context.router.pushNamed('/details');
Navegue de volta ao local anterior chamando context.router.back()
// navigate to previous location context.router.back();
Você pode até adicionar uma lista de rotas à pilha de páginas
// adiciona uma lista de rotas à pilha de páginas context.router.pushAll([ const HomeRoute(), const DetailsRoute(), ]);
Passando Argumentos
O AutoRoute
detecta e manipula automaticamente os argumentos da página para você, o objeto de rota gerado fornecerá todos os argumentos de que sua página precisa, incluindo parâmetros de caminho/consulta
por exemplo, pense em nossa HomePage
, se adicionarmos um argumento userId
a ela, quando nosso HomeRoute
for gerado, ele já terá esse argumento criado e, se dissermos que esse argumento é obrigatório, o AutoRoute irá lidar automaticamente com isso
@RoutePage() class HomePage extends StatelessWidget { const HomePage({super.key, required this.userId}); final String userId; @override Widget build(BuildContext context) { return const Placeholder(); } }
Quando geramos nosso HomeRoute
e tentamos usar, ele vai para o argumento userId
obrigatório
// O argumento `userId` é necessário, pois especificamos essa forma na página inicial context.router.push(HomeRoute(userId: 'Sergio'));
Trabalhando com Caminhos
Trabalhar com caminhos no AutoRoute é opcional porque os objetos PageRouteInfo
são correspondidos por nome, a menos que sejam enviados como uma string usando a propriedade deepLinkBuilder no delegado raiz ou nos métodos pushNamed
, replaceNamed
browseNamed
.
Ao desenvolver um aplicativo que exija links diretos, você provavelmente precisaria definir caminhos com nomes claros e memoráveis, e isso é feito usando o argumento path em AutoRoute
.
AutoRoute(path: '/books', page: BookListPage),
Você pode definir parâmetros de caminho prefixando-os com dois pontos
AutoRoute(path: '/books/:id', page: BookDetailsPage),
Em seguida, você pode extrair os parâmetros de caminho do caminho anotando os parâmetros do construtor com @PathParam('name')
com o mesmo nome do segmento.
class BookDetailsPage extends StatelessWidget { const BookDetailsPage({@PathParam('id') this.bookId}); final int bookId; ...
Agora escrever /books/1
no navegador irá navegar para BookDetailsPage e extrair automaticamente o argumento bookId
do caminho e injetá-lo em seu widget.
Trabalhando com parâmetros de consulta
Da mesma maneira antiga, simplesmente anote o parâmetro do construtor para manter o valor do parâmetro de consulta com @QueryParam('name')
e deixe o AutoRoute fazer o resto.
RouteData.of(context).pathParams; // ou usando a extensão context.routeData.queryParams
NOTA se o nome do seu parâmetro for o mesmo que o parâmetro path/query, você pode usar o const
@pathParam
ou@queryParam
e não passar um@PathParam('name')
.
@RoutePage() class BookDetailsPage extends StatelessWidget { // como o parâmetro do widget e o parâmetro do caminho são os mesmos, não é necessário adicionar @PathParam('id') const BookDetailsPage({@pathParam this.id}); final int id; ...
Caminhos de Redirecionamento
Os caminhos podem ser redirecionados usando RedirectRoute
. A configuração a seguir nos levará para /feed
quando /
for correspondido.
<AutoRoute> [ RedirectRoute(path: '/', redirectTo: '/feed'), AutoRoute(path: '/feed', page: FeedRoute.page), ]
Guardas de rota
Os guardas são úteis para restringir o acesso a determinadas rotas, funcionam como middleware, o que significa que as rotas não podem ser adicionadas à pilha sem passar pelos guardas atribuídos.
Criamos um guarda de rota estendendo AutoRouteGuard
, implementamos onNavigation
e adicionamos nossa lógica lá.
class AuthGuard extends AutoRouteGuard { @override void onNavigation(NavigationResolver resolver, StackRouter router) { // vamos assumir que o usuário está autenticado const authenticated = true; // a navegação é pausada até resolver.next() ser chamado com // true para retomar/continuar a navegação ou false para abortar a navegação if (authenticated) { // se o usuário estiver autenticado, continue resolver.next(true); } else { // redirecionamos o usuário para nossa página de login router.push(LoginRoute(onResult: (success) { // se sucesso == true a navegação será retomada // senão será abortado resolver.next(success); })); } } }
Agora atribuímos nossa guarda às rotas que queremos proteger.
AutoRoute(page: FeedRoute.page, guards: [AuthGuard()]);
Guardas de rota globais
Você também pode adicionar guarda a todas as suas rotas de pilha. Digamos que você tenha um aplicativo sem telas públicas, você precisa ter uma guarda global e pode fazer isso fazendo com que seu router
implemente o AutoRouteGuard
@AutoRouterConfig() class AppRouter extends _$AppRouter implements AutoRouteGuard { @override void onNavigation(NavigationResolver resolver, StackRouter router) { if(isAuthenticated || resolver.route.name == LoginRoute.name){ // continuamos a navegação resolver.next(); }else{ // caso contrário, navegaremos para a página de login para sermos autenticados push(LoginRoute(onResult:(didLogin)=> resolver.next(didLogin))) } } // ..routes[] }
Usando links diretos
O AutoRoute lidará automaticamente com links diretos vindos da plataforma.
O AutoRoute possui um construtor de links diretos, um interceptador de links diretos onde você pode validar ou substituir links diretos vindos da plataforma.
No exemplo a seguir, permitiremos apenas links diretos começando com /products
MaterialApp.router( routerConfig: _appRouter.config( deepLinkBuilder: (deepLink){ if(deepLink.path.startsWith('/products'){ // continua com o link da plataforma return deepLink; }else{ return DeepLink.defaultPath; // ou DeepLink.path('/') // ou DeepLink([HomeRoute()]) } } ), )
Nested navigation
Nesting navigators significa renderizar um navegador dentro de uma tela de outro navegador.
Para definir rotas aninhadas nós apenas
- Adicione nossas rotas aninhadas como
children
da rota pai. - Adicione o widget
AutoRouter
para renderizar rotas aninhadas
Vamos adicionar nossos nested children
Estamos supondo que AudioRoute e VideoRoute já foram gerados 🙂
@AutoRouterConfig() class AppRouter extends _$AppRouter { @override List<AutoRoute> get routes => [ AutoRoute( initial: true, path: '/feed', page: FeedRoute.page, children: [ // `AudioPage` & `VideoPage` são nested children de `FeedPage` AutoRoute(path: 'audio', page: AudioRoute.page), AutoRoute(path: 'video', page: VideoRoute.page), ], ), AutoRoute(page: CoursesRoute.page), ]; }
Podemos definir initial=true
ou path=’’
para fazer um children ao acessar /feed
Agora que temos nossas nested routes, vamos adicionar o widget AutoRouter
para renderizá-las.
@RoutePage() class FeedPage extends StatelessWidget { const FeedPage({super.key}); @override Widget build(BuildContext context) { return const Scaffold( body: Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Note: RouterButton é apenas um botão que chama router.push(destination) RouterButton(label: 'Audio', destination: AudioRoute()), RouterButton(label: 'Video', destination: VideoRoute()), ], ), Expanded( // nested routes será renderizado aqui child: AutoRouter(), ) ], ), ), ); } }
Agora, se navegarmos para /feed/audio
, seremos levados para a FeedPage
e a AudioPage
será mostrada dentro dela 👇🏿👇🏿
Coisas a ter em mente ao implementar a nested navigation
- Cada roteador gerencia sua própria pilha de páginas.
- Ações de navegação como push, pop e amigos são tratadas pelo roteador superior e surgem se não puderem ser tratadas.
Nested Tab navigation
Alguns aplicativos exibem destinos em uma subseção da tela, por exemplo, um BottomNavigationBar que permanece na tela ao navegar entre as telas.
Como essa não é uma tarefa fácil de implementar com aplicativos Flutter, o auto_route torna tabs navigation o mais fácil e direta possível.
Usaremos o AutoTabsRouter
para lidar com tab navigation
@RoutePage() class FeedPage extends StatelessWidget { const FeedPage({super.key}); @override Widget build(BuildContext context) { // usaremos [AutoTabsRouter] para lidar com a navegação por guias return AutoTabsRouter( // lista de suas tab routes routes: const [ AudioRoute(), VideoRoute(), ], transitionBuilder: (context, child, animation) { // adiciona animação à nossa página da guia selecionada return FadeTransition(opacity: animation, child: child); }, builder: (context, child) { final tabsRouter = AutoTabsRouter.of(context); // [ScaffoldWithNavbar] é um widget simples que retorna um Scaffold // e definir a rota ativa em BottomNavigationBar Tap return ScaffoldWithNavbar(tabsRouter, child); }, ); } }
Acabamos de adicionar o AutoTabsRouter
para lidar com tabs navigation. Isso requer:
routers
: sua lista detab routes
transactionBuilder (opcional)
: a animação da transação ao alterar as tabsbuilder
: qual página será renderizada, normalmente você vai retornar um Scaffold com BottomNavigationBar.
No exemplo anterior retornamos um ScaffoldWithNavbar, vejamos se está definido
class ScaffoldWithNavbar extends StatelessWidget { const ScaffoldWithNavbar(this.tabsRouter, this.child, {super.key}); final TabsRouter tabsRouter; final Widget child; @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: BottomNavigationBar( currentIndex: tabsRouter.activeIndex, items: const [ BottomNavigationBarItem(icon: Icon(Icons.audio_file), label: 'Audio'), BottomNavigationBarItem(icon: Icon(Icons.videocam_off), label: 'Video'), ], onTap: (index) { // troca o current tab tabsRouter.setActiveIndex(index); }, ), body: child, ); } }
Simples né? 🙃 após esta configuração, teremos o seguinte resultado, navegação de abas aninhadas implementada com sucesso
Você pode obter o mesmo resultado usando um AutoTabsScaffold
fornecido que torna as coisas muito mais limpas
class FeedPage extends StatelessWidget { @override Widget build(BuildContext context) { return AutoTabsScaffold( routes: const [ AudioRoute(), VideoRoute(), ], bottomNavigationBuilder: (_, tabsRouter) { return BottomNavigationBar( currentIndex: tabsRouter.activeIndex, onTap: tabsRouter.setActiveIndex, items: const [ BottomNavigationBarItem(label: 'Audio',icon: Icon(Icons.audio)), BottomNavigationBarItem(label: 'Video',icon: Icon(Icons.videocam_off)), ], ); }, ); } }
O AutoTabsRouter
também tem suporte para PageView
e TabBar
, para isso basta chamar um construtor diferente para cada um. Segue exemplos de código abaixo 👇🏿
Nested PageView navigation
Use o construtor AutoTabsRouter.pageView
para implementar tabs usando PageView
@RoutePage() class FeedPage extends StatelessWidget { const FeedPage({super.key}); @override Widget build(BuildContext context) { // usaremos [AutoTabsRouter.pageView] para renderizar exibição de página return AutoTabsRouter.pageView( routes: const [ AudioRoute(), VideoRoute(), ], builder: (context, child, _) { final tabsRouter = AutoTabsRouter.of(context); // [ScaffoldWithNavbar] é apenas um widget criado que contém um [BottomNavigationBarItem] return ScaffoldWithNavbar(tabsRouter, child); }, ); } }
Nested TabBar navigation
Use o construtor AutoTabsRouter.tabBar para implementar tabs usando TabBar
@RoutePage() class FeedPage extends StatelessWidget { const FeedPage({super.key}); @override Widget build(BuildContext context) { // usaremos [AutoTabsRouter.tabBar] para renderizar TabBar return AutoTabsRouter.tabBar( routes: const [ AudioRoute(), VideoRoute(), ], builder: (context, child, tabController) { final tabsRouter = AutoTabsRouter.of(context); // [ScaffoldWithTabBar] é apenas um widget criado que contém um [TabBar] return ScaffoldWithTabBar(tabsRouter, child, tabController); }, ); } }
O AutoRoute capacita os desenvolvedores a gerenciar com eficiência os fluxos de navegação, aprimorar as experiências do usuário e acelerar seus fluxos de trabalho de desenvolvimento.
Espero que tenham gostado da viagem para auto_route 😊🥳