Flutter Navigation 2.0 e GoRouter em 2026: Deep Linking e Perguntas de Entrevista

Dominar a navegação Flutter com GoRouter 17.5: roteamento declarativo, deep linking, ShellRoute, guards de rota e perguntas de entrevista com exemplos práticos.

Diagrama de arquitetura de navegação Flutter com fluxo de deep linking GoRouter

A navegação Flutter evoluiu significativamente desde a introdução do Navigation 2.0. O GoRouter 17.5, o pacote oficial da equipe Flutter, agora lida com a complexidade da API Router enquanto fornece roteamento declarativo, deep linking automático e navegação aninhada prontos para uso.

GoRouter está funcionalmente completo

A equipe Flutter considera o GoRouter funcionalmente completo desde 2026. Ele suporta parâmetros de caminho, parâmetros de consulta, redirecionamentos, ShellRoute para UI persistente e StatefulShellRoute para preservar o estado das abas. O Navigator 2.0 bruto raramente é escrito manualmente em produção.

Compreendendo a Arquitetura do Navigator 2.0

O Navigator 2.0 introduziu uma abordagem declarativa baseada em URLs para navegação Flutter. Em vez de chamadas imperativas push e pop, o estado da aplicação determina o que aparece na pilha de navegação. A arquitetura se baseia em três classes fundamentais:

  • Router: O widget de nível superior que coordena a navegação
  • RouteInformationParser: Traduz URLs em estado de aplicação
  • RouterDelegate: Constrói a árvore de widgets com base nesse estado

Quando um deep link chega do sistema operacional ou uma URL muda em um navegador web, o framework atualiza o estado e a UI reage. O problema: escrever um RouterDelegate e RouteInformationParser personalizados manualmente requer código boilerplate considerável.

raw_navigator2_example.dartdart
// Raw Navigator 2.0 approach - verbose and rarely used directly
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
  
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  AppRoutePath? _currentPath;

  
  AppRoutePath? get currentConfiguration => _currentPath;

  
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        const MaterialPage(child: HomeScreen()),
        if (_currentPath?.isProductPage == true)
          MaterialPage(child: ProductScreen(id: _currentPath!.productId!)),
      ],
      onDidRemovePage: (page) {
        // Handle page removal
      },
    );
  }

  
  Future<void> setNewRoutePath(AppRoutePath path) async {
    _currentPath = path;
    notifyListeners();
  }
}

Essa verbosidade é o motivo da existência do GoRouter. Ele encapsula o Navigator 2.0 e lida com o código boilerplate.

Configuração do GoRouter 17.5

O GoRouter 17.5 introduz suporte para metadados de rota e restrições com expressões regulares para parâmetros de caminho. O SDK mínimo requerido é Flutter 3.32 e Dart 3.8.

router_config.dartdart
import 'package:go_router/go_router.dart';

final goRouter = GoRouter(
  initialLocation: '/',
  debugLogDiagnostics: true, // Useful during development
  routes: [
    GoRoute(
      path: '/',
      name: 'home',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'products/:id', // Path parameter
          name: 'product',
          builder: (context, state) {
            final productId = state.pathParameters['id']!;
            return ProductScreen(productId: productId);
          },
        ),
        GoRoute(
          path: 'search',
          name: 'search',
          builder: (context, state) {
            // Query parameters: /search?q=flutter&category=books
            final query = state.uri.queryParameters['q'] ?? '';
            final category = state.uri.queryParameters['category'];
            return SearchScreen(query: query, category: category);
          },
        ),
      ],
    ),
  ],
);

O router se integra com MaterialApp.router:

main.dartdart
import 'package:flutter/material.dart';
import 'router_config.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: goRouter,
      title: 'GoRouter Demo',
    );
  }
}

Configuração de Deep Linking para Android e iOS

O deep linking permite que URLs externas abram telas específicas no aplicativo. O GoRouter lida com o roteamento automaticamente uma vez que a configuração da plataforma está pronta.

O Android requer um intent-filter no AndroidManifest.xml e um arquivo Digital Asset Links hospedado no domínio:

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
  <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
      android:scheme="https"
      android:host="example.com"
      android:pathPrefix="/products" />
  </intent-filter>
</activity>

O arquivo assetlinks.json deve ser servido em https://example.com/.well-known/assetlinks.json.

Para iOS, a capacidade Associated Domains deve ser habilitada no Xcode, e um arquivo apple-app-site-association deve ser hospedado:

https://example.com/.well-known/apple-app-site-associationjson
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.example.app",
        "paths": ["/products/*", "/search"]
      }
    ]
  }
}

O GoRouter 17.5 corrigiu um bug crítico onde deep links do Android em inicialização a frio com caminhos vazios perdiam o esquema e a autoridade. Isso significa que os deep links agora funcionam de forma confiável mesmo quando o aplicativo não está em execução.

Testando deep links

Para testar deep links no Android usar adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123". Para iOS, usar xcrun simctl openurl booted "https://example.com/products/123".

ShellRoute para UI de Navegação Persistente

ShellRoute envolve as rotas filhas com um elemento de UI persistente como uma BottomNavigationBar ou um Drawer. O shell permanece visível enquanto navega entre seus filhos.

shell_router_config.dartdart
final goRouter = GoRouter(
  initialLocation: '/home',
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithNavBar(child: child);
      },
      routes: [
        GoRoute(
          path: '/home',
          name: 'home',
          builder: (context, state) => const HomeTab(),
        ),
        GoRoute(
          path: '/explore',
          name: 'explore',
          builder: (context, state) => const ExploreTab(),
        ),
        GoRoute(
          path: '/profile',
          name: 'profile',
          builder: (context, state) => const ProfileTab(),
        ),
      ],
    ),
  ],
);

// scaffold_with_nav_bar.dart
class ScaffoldWithNavBar extends StatelessWidget {
  final Widget child;
  const ScaffoldWithNavBar({super.key, required this.child});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _calculateSelectedIndex(context),
        onTap: (index) => _onItemTapped(index, context),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.explore), label: 'Explore'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }

  int _calculateSelectedIndex(BuildContext context) {
    final location = GoRouterState.of(context).uri.toString();
    if (location.startsWith('/explore')) return 1;
    if (location.startsWith('/profile')) return 2;
    return 0;
  }

  void _onItemTapped(int index, BuildContext context) {
    switch (index) {
      case 0: context.go('/home');
      case 1: context.go('/explore');
      case 2: context.go('/profile');
    }
  }
}

O GoRouter 17.5 corrigiu um problema onde os gestos de voltar no iOS removiam todo o ShellRoute em vez de apenas a sub-rota ativa.

Pronto para mandar bem nas entrevistas de Flutter?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

StatefulShellRoute para Preservar Estado das Abas

Quando os usuários trocam de aba, ShellRoute reconstrói o filho. StatefulShellRoute preserva a pilha de navegação de cada branch:

stateful_shell_config.dartdart
final goRouter = GoRouter(
  initialLocation: '/home',
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return ScaffoldWithNavBar(navigationShell: navigationShell);
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => const HomeTab(),
              routes: [
                GoRoute(
                  path: 'details/:id',
                  builder: (context, state) => DetailsScreen(
                    id: state.pathParameters['id']!,
                  ),
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/explore',
              builder: (context, state) => const ExploreTab(),
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/profile',
              builder: (context, state) => const ProfileTab(),
            ),
          ],
        ),
      ],
    ),
  ],
);

Com essa configuração, se um usuário navega para /home/details/42, muda para a aba Explorar, depois retorna para Início, ele ainda estará na tela de detalhes.

Guards de Rota com Redirect

Os redirecionamentos lidam com verificações de autenticação e navegação condicional. O callback redirect é executado antes de cada navegação:

authenticated_router.dartdart
final goRouter = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    final isLoggedIn = AuthService.instance.isLoggedIn;
    final isLoggingIn = state.matchedLocation == '/login';

    // Redirect to login if not authenticated
    if (!isLoggedIn && !isLoggingIn) {
      return '/login?redirect=${state.uri}';
    }

    // Redirect away from login if already authenticated
    if (isLoggedIn && isLoggingIn) {
      final redirect = state.uri.queryParameters['redirect'];
      return redirect ?? '/';
    }

    return null; // No redirect
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginScreen(),
    ),
    GoRoute(
      path: '/admin',
      redirect: (context, state) {
        // Route-level redirect for admin-only access
        if (!AuthService.instance.isAdmin) {
          return '/';
        }
        return null;
      },
      builder: (context, state) => const AdminScreen(),
    ),
  ],
);

O GoRouter 17.5 adicionou suporte para usar métodos de extensão de contexto como context.namedLocation() dentro dos callbacks de redirecionamento.

Perguntas de Entrevista Comuns sobre Navegação Flutter

Entrevistadores frequentemente avaliam o conhecimento de navegação porque toca arquitetura, gerenciamento de estado e integração de plataforma. Aqui estão perguntas que distinguem candidatos seniores:

P: Qual problema o Navigator 2.0 resolve que o Navigator 1.0 não resolve?

O Navigator 1.0 usa navegação imperativa: Navigator.push() e Navigator.pop(). A aplicação não pode representar o estado de navegação como uma URL, tornando difícil o deep linking e o suporte web. O Navigator 2.0 é declarativo: o estado da aplicação determina a pilha de navegação, URLs são cidadãos de primeira classe, e deep links funcionam automaticamente.

P: Quando usar context.go() vs context.push()?

context.go('/path') substitui a pilha de navegação atual até a rota correspondente. context.push('/path') adiciona uma nova rota no topo da pilha existente. Usar go() para navegação de nível superior (trocar abas, ir para início) e push() para aprofundar em detalhes enquanto preserva a navegação de volta.

P: Como preservar o estado entre trocas de abas?

Usar StatefulShellRoute.indexedStack(). Ele mantém pilhas de navegação separadas para cada branch e as preserva ao trocar de aba. Sem isso, trocar de aba reconstrói toda a sub-árvore.

P: Como passar objetos complexos entre rotas?

Existem três abordagens. Primeiro, passar um ID no parâmetro de caminho e buscar o objeto na tela de destino. Segundo, usar o parâmetro extra do GoRouter para passar o objeto diretamente. Terceiro, armazenar o objeto em uma solução de gerenciamento de estado (Riverpod, Bloc) e acessá-lo do destino. A primeira abordagem é melhor para deep linking porque a URL permanece compartilhável.

dart
// Passing via extra (not deep-linkable)
context.push('/product', extra: productObject);

// Receiving via extra
GoRoute(
  path: '/product',
  builder: (context, state) {
    final product = state.extra as Product?;
    return ProductScreen(product: product);
  },
),

P: O que é o callback onEnter no GoRouter 17.5?

O callback onEnter é executado ao entrar em uma rota e fornece acesso tanto ao estado de rota atual quanto ao próximo. Ele permite executar lógica antes que a rota seja completamente carregada, útil para analytics ou pré-carregamento condicional.

Rotas Type-Safe com Geração de Código

O GoRouter suporta roteamento type-safe através de geração de código. Basta definir classes de dados de rota e deixar o build_runner gerar o código de navegação:

routes.dartdart
import 'package:go_router/go_router.dart';

part 'routes.g.dart';

<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
  const HomeRoute();

  
  Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}

<ProductRoute>(path: '/product/:id')
class ProductRoute extends GoRouteData {
  final String id;
  const ProductRoute({required this.id});

  
  Widget build(BuildContext context, GoRouterState state) =>
      ProductScreen(productId: id);
}

Após executar dart run build_runner build, a navegação tem segurança em tempo de compilação:

dart
// Type-safe navigation
ProductRoute(id: '123').go(context);

Isso elimina erros de digitação nos caminhos de rota e garante que os parâmetros sejam sempre fornecidos.

Dominando a Navegação Flutter para Aplicações de Produção

  • GoRouter 17.5 é a solução de navegação recomendada para aplicações Flutter que requerem deep linking, navegação aninhada ou suporte web
  • Usar ShellRoute para barras de navegação inferiores persistentes; usar StatefulShellRoute para preservar a pilha de navegação de cada aba
  • Configurar o deep linking específico de plataforma (Android App Links, iOS Universal Links) separadamente do GoRouter, que lida com a lógica de roteamento
  • Implementar guards de autenticação com o callback redirect no nível do router ou no nível de rota individual
  • Para cenários de entrevista, focar em explicar o modelo declarativo: o estado da aplicação direciona a UI, URLs são derivadas do estado, e o GoRouter lida com a tradução
  • Rotas type-safe com geração de código eliminam erros de tempo de execução por caminhos mal digitados
  • Consultar a documentação de navegação Flutter e o changelog do GoRouter para recursos específicos por versão

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Flutter?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 23 de agosto de 2026

Tags

#flutter
#navigation
#gorouter
#deep-linking
#mobile

Compartilhar

Artigos relacionados