Flutter guia auto_route

Tempo de leitura: 8 minutes

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:

  1. Crie uma classe de roteador e anote-a com @AutoRouterConfig, em seguida, estenda “_$YourClassName”
  2. 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 ??? 🙃

  1. escolha qual página você deseja rotear
  2. 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

  1. Adicione nossas rotas aninhadas como children da rota pai.
  2. 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

  1. Cada roteador gerencia sua própria pilha de páginas.
  2. 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:

  1. routers : sua lista de tab routes
  2. transactionBuilder (opcional): a animação da transação ao alterar as tabs
  3. builder: 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 😊🥳