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.
