Hai mai aperto un'app e dopo un tap sei finito in una schermata sbagliata? O peggio, un utente arriva da un link e vede la home invece del prodotto che aveva cercato su Google? Succede quando la navigazione è gestita male. Noi, di Meteora Web, abbiamo visto progetti dove la gestione delle route era un groviglio di push e pop senza logica. Ecco come mettere ordine.
Perché la navigazione in Flutter è diversa da altre tecnologie?
Flutter non usa il sistema di view nativo di iOS o Android. Ha un suo motore grafico (Skia / Impeller) e un proprio stack di routing. Questo significa che la logica di navigazione è interamente sotto il tuo controllo, ma anche che devi conoscerla a fondo per evitare bug subdoli.
Il widget Navigator funziona come una pila (stack): ogni schermata è una Route che viene aggiunta o rimossa. Sembra semplice, ma quando devi gestire autenticazione, deep link, tab bar e passaggio dati, il gioco si fa complesso.
Come si definiscono le route in Flutter?
Ci sono due approcci principali:
Route dichiarative (Navigator 2.0)
Con Router e RouteInformationParser gestisci la navigazione come stato dell'app. Ideale per app complesse e deep link. Noi usiamo il pacchetto go_router per semplificare.
Sponsored Protocol
Route imperative (Navigator 1.0)
Il classico Navigator.push e pop. Va bene per app piccole, ma scala male.
// Navigator 1.0
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DettaglioProdotto(id: 42)),
);Il problema? Se l'utente riceve una notifica push con un ID prodotto diverso, devi gestire manualmente la route. Con Navigator 2.0 e go_router, invece, il percorso è dichiarato centralmente.
Come gestire i deep link con Flutter e GoRouter?
I deep link permettono a un utente di arrivare direttamente a una schermata specifica dell'app da un link esterno (es. email, annuncio Google). In Flutter, devi configurare due cose: il sistema operativo (iOS Universal Links, Android App Links) e il router lato Dart.
Con go_router, la configurazione è lineare:
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DettaglioProdotto(id: int.parse(id));
},
routes: [
GoRoute(
path: 'reviews',
builder: (context, state) => RecensioniProdotto(id: id),
),
],
),
],
);Poi, nel main.dart, avvolgi l'app nel MaterialApp.router:
Sponsored Protocol
MaterialApp.router(
routerConfig: router,
// ...
);Per i deep link nativi, devi anche aggiungere il file apple-app-site-association (iOS) e assetlinks.json (Android). Noi, di Meteora Web, abbiamo automatizzato questo passaggio con uno script che genera i file e li carica sul server.
Passaggio dati tra schermate: cosa evitare?
Errore comune: passare oggetti complessi come Map o serializzare tutto in JSON. In Flutter, la via più pulita è usare i costruttori delle route. Con go_router, puoi passare extra:
context.go('/product/42', extra: {'utm': 'email', 'campaign': 'spring'});Poi nel builder li leggi con state.extra. Semplice e tracciabile.
Navigazione condizionale: login, onboarding e stato utente
Molte app devono reindirizzare l'utente in base allo stato di autenticazione. Con go_router puoi usare redirect:
Sponsored Protocol
final router = GoRouter(
redirect: (context, state) {
final isLoggedIn = AuthService.isLoggedIn;
final isOnLoginPage = state.matchedLocation == '/login';
if (!isLoggedIn && !isOnLoginPage) return '/login';
if (isLoggedIn && isOnLoginPage) return '/';
return null;
},
// ...
);Attenzione: non fare redirect in loop. return null significa nessun reindirizzamento.
Come testare la navigazione in Flutter?
Noi scriviamo test widget per ogni route critica. Con go_router puoi creare un router di test e iniettare dipendenze fittizie:
testWidgets('Deep link apre la schermata prodotto', (tester) async {
await tester.pumpWidget(
MaterialApp.router(
routerConfig: GoRouter(
initialLocation: '/product/123',
routes: [/* ... */],
),
),
);
expect(find.text('Dettaglio Prodotto 123'), findsOneWidget);
});Non dimenticare i test di integrazione per deep link reali: simula un link in arrivo dal sistema operativo.
Errori comuni nella navigazione Flutter e come evitarli
Non usare Navigator.push dentro un callback asincrono senza il contesto giusto
Il context potrebbe essere smontato. Usa context.mounted (Flutter 3.7+) o un navigatorKey globale.
Sponsored Protocol
Ignorare il PopScope (o WillPopScope)
Se l'utente preme back durante un salvataggio, puoi mostrare un dialog. Con PopScope blocchi il pop finché non è sicuro.
PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
// Mostra dialog di conferma
showDialog(...);
}
},
child: Scaffold(...),
);Cosa fare adesso
Ecco tre azioni concrete per migliorare la navigazione nella tua app Flutter:
- Sostituisci
Navigator.pushcon go_router se hai più di 5 schermate. Scarica il pacchetto:flutter pub add go_router. - Configura i deep link per iOS e Android seguendo la guida ufficiale Flutter. Crea i file JSON e verificali con lo strumento di test di Google.
- Aggiungi un redirect di autenticazione usando la proprietà
redirectdi GoRouter. Provalo con un utente non loggato.
Vuoi approfondire? Leggi la nostra Pillar su Flutter per una visione completa dello sviluppo cross-platform.