Perché go_router

La navigazione è uno dei pilastri di ogni applicazione mobile. Per anni gli sviluppatori Flutter hanno usato il Navigator 1.0 con i suoi metodi imperativi come Navigator.push e Navigator.pop. Con l'arrivo del Navigator 2.0 è diventato possibile gestire le rotte in modo dichiarativo, ma la sua API risultava verbosa e complessa.

go_router, mantenuto dal team Flutter, nasce proprio per semplificare il Navigator 2.0 offrendo un'API pulita, supporto nativo al deep linking, ai redirect e alle rotte annidate. È oggi la scelta consigliata ufficialmente per progetti di media e grande dimensione.

Installazione

Aggiungi il pacchetto al tuo pubspec.yaml:

dependencies:
  go_router: ^14.0.0

Poi esegui flutter pub get.

Configurazione di base

Definiamo un router con due rotte semplici:

import 'package:go_router/go_router.dart';

final GoRouter router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      name: 'home',
      builder: (context, state) => const HomePage(),
    ),
    GoRoute(
      path: '/dettaglio',
      name: 'dettaglio',
      builder: (context, state) => const DettaglioPage(),
    ),
  ],
);

Colleghiamo il router all'applicazione usando MaterialApp.router:

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Demo go_router',
      routerConfig: router,
    );
  }
}

Navigare tra le pagine

Con go_router puoi spostarti tra le rotte usando il BuildContext:

// Sostituisce lo stack corrente
context.go('/dettaglio');

// Impila una nuova rotta (push classico)
context.push('/dettaglio');

// Navigazione per nome con parametri
context.goNamed('dettaglio');

La differenza è importante: go aggiorna la posizione e ricostruisce lo stack, mentre push aggiunge una pagina sopra quella attuale, mantenendo la possibilità di tornare indietro.

Parametri di percorso e query

Una delle funzionalità più utili è il passaggio di parametri direttamente dall'URL:

GoRoute(
  path: '/prodotto/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    final filtro = state.uri.queryParameters['filtro'];
    return ProdottoPage(id: id, filtro: filtro);
  },
),

Per navigare verso questa rotta:

context.go('/prodotto/42?filtro=offerte');

Rotte annidate con ShellRoute

Quando vuoi mantenere un'interfaccia persistente (come una BottomNavigationBar) mentre cambi il contenuto interno, ShellRoute è la soluzione ideale:

ShellRoute(
  builder: (context, state, child) {
    return ScaffoldConMenu(child: child);
  },
  routes: [
    GoRoute(
      path: '/feed',
      builder: (context, state) => const FeedPage(),
    ),
    GoRoute(
      path: '/profilo',
      builder: (context, state) => const ProfiloPage(),
    ),
  ],
),

Il widget child rappresenta la pagina attiva, mentre lo ScaffoldConMenu rimane fisso.

Redirect e protezione delle rotte

Il parametro redirect permette di reindirizzare l'utente in base allo stato dell'applicazione, ad esempio per proteggere le pagine riservate agli utenti autenticati:

final GoRouter router = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    final loggato = AuthService.instance.isLoggedIn;
    final staAndandoAlLogin = state.matchedLocation == '/login';

    if (!loggato && !staAndandoAlLogin) {
      return '/login';
    }
    if (loggato && staAndandoAlLogin) {
      return '/';
    }
    return null; // nessun redirect
  },
  routes: [ /* ... */ ],
);

Restituendo null non viene effettuato alcun reindirizzamento; restituendo un percorso, go_router porta l'utente alla nuova destinazione.

Gestione degli errori

Puoi definire una pagina personalizzata per le rotte non trovate tramite errorBuilder:

GoRouter(
  errorBuilder: (context, state) => ErrorPage(error: state.error),
  routes: [ /* ... */ ],
);

Conclusioni

go_router rende la navigazione in Flutter prevedibile, scalabile e perfettamente integrata con il deep linking del web e del mobile. Grazie a ShellRoute, ai redirect e ai parametri tipizzati puoi costruire flussi di navigazione complessi mantenendo il codice leggibile. Se stai avviando un nuovo progetto o pensando di refactorizzare la navigazione esistente, go_router è oggi la scelta più solida e supportata ufficialmente.