Flutter Navigation 2.0 e GoRouter nel 2026: Deep Linking e Domande da Colloquio
Padroneggiare la navigazione Flutter con GoRouter 17.5: routing dichiarativo, deep linking, ShellRoute, route guard e domande frequenti nei colloqui con esempi pratici.

La navigazione Flutter si è evoluta significativamente dall'introduzione di Navigation 2.0. GoRouter 17.5, il pacchetto ufficiale del team Flutter, gestisce la complessità della Router API offrendo routing dichiarativo, deep linking automatico e navigazione annidata di default.
Il team Flutter considera GoRouter feature-complete dal 2026. Supporta parametri di percorso, parametri di query, redirect, ShellRoute per UI persistente e StatefulShellRoute per preservare lo stato delle tab. Navigator 2.0 raw viene raramente implementato manualmente in produzione.
Comprendere l'architettura di Navigator 2.0
Navigator 2.0 ha introdotto un approccio dichiarativo e guidato da URL per la navigazione Flutter. Invece di chiamate imperative push e pop, lo stato dell'app determina cosa appare nello stack di navigazione. L'architettura si basa su tre classi principali:
- Router: Il widget di livello superiore che coordina la navigazione
- RouteInformationParser: Traduce gli URL in stato dell'app
- RouterDelegate: Costruisce l'albero dei widget basandosi su quello stato
Quando un deep link arriva dal sistema operativo o un URL cambia nel browser web, il framework aggiorna lo stato e la UI reagisce. Il problema: scrivere un RouterDelegate e RouteInformationParser personalizzato a mano richiede un boilerplate significativo.
// 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();
}
}Questa verbosità è il motivo per cui esiste GoRouter. Incapsula Navigator 2.0 e gestisce il boilerplate.
Configurare GoRouter 17.5
GoRouter 17.5 introduce il supporto per i metadati delle route e vincoli con espressioni regolari per i parametri di percorso. Il requisito minimo è Flutter 3.32 e Dart 3.8.
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);
},
),
],
),
],
);Il router si integra con MaterialApp.router:
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',
);
}
}Configurazione del Deep Linking per Android e iOS
Il deep linking permette agli URL esterni di aprire schermate specifiche nell'app. GoRouter gestisce il routing automaticamente una volta che la configurazione della piattaforma è in atto.
Android App Links
Android richiede un intent-filter in AndroidManifest.xml e un file Digital Asset Links ospitato sul dominio:
<!-- 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>Il file assetlinks.json deve essere servito all'indirizzo https://example.com/.well-known/assetlinks.json.
iOS Universal Links
Per iOS, la capability Associated Domains deve essere abilitata in Xcode, e un file apple-app-site-association deve essere ospitato:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": ["/products/*", "/search"]
}
]
}
}GoRouter 17.5 ha corretto un bug critico in cui i deep link Android con cold-start con percorsi vuoti perdevano schema e authority. Questo significa che i deep link ora funzionano in modo affidabile anche quando l'app non è in esecuzione.
Testare i deep link Android con adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123". Per iOS, usare xcrun simctl openurl booted "https://example.com/products/123".
ShellRoute per UI di navigazione persistente
ShellRoute avvolge le route figlie con un elemento UI persistente come una BottomNavigationBar o un Drawer. La shell rimane visibile durante la navigazione tra i suoi figli.
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');
}
}
}GoRouter 17.5 ha risolto un problema in cui i gesti di ritorno su iOS facevano il pop dell'intera ShellRoute invece che solo della sotto-route attiva.
Pronto a superare i tuoi colloqui su Flutter?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
StatefulShellRoute per preservare lo stato delle tab
Quando gli utenti cambiano tab, ShellRoute ricostruisce il figlio. StatefulShellRoute preserva lo stack di navigazione di ogni branch:
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(),
),
],
),
],
),
],
);Con questa configurazione, se un utente naviga a /home/details/42, passa alla tab Explore, poi torna a Home, sarà ancora sulla schermata dei dettagli.
Route Guard con Redirect
I redirect gestiscono i controlli di autenticazione e la navigazione condizionale. Il callback redirect viene eseguito prima di ogni navigazione:
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(),
),
],
);GoRouter 17.5 ha aggiunto il supporto per l'uso di metodi di estensione del context come context.namedLocation() all'interno dei callback di redirect.
Domande frequenti sulla navigazione Flutter nei colloqui
Gli intervistatori testano frequentemente la conoscenza della navigazione perché tocca architettura, gestione dello stato e integrazione con la piattaforma. Ecco le domande che distinguono i candidati senior:
D: Quale problema risolve Navigator 2.0 che Navigator 1.0 non risolve?
Navigator 1.0 usa la navigazione imperativa: Navigator.push() e Navigator.pop(). L'app non può rappresentare lo stato di navigazione come URL, rendendo difficile il deep linking e il supporto web. Navigator 2.0 è dichiarativo: lo stato dell'app determina lo stack di navigazione, gli URL sono cittadini di prima classe, e i deep link funzionano automaticamente.
D: Quando si userebbe context.go() vs context.push()?
context.go('/path') sostituisce lo stack di navigazione corrente fino alla route corrispondente. context.push('/path') aggiunge una nuova route sopra lo stack esistente. Si usa go() per la navigazione di livello superiore (cambio tab, andare alla home) e push() per approfondire i dettagli preservando la navigazione indietro.
D: Come si preserva lo stato tra i cambi di tab?
Si usa StatefulShellRoute.indexedStack(). Mantiene stack di navigazione separati per ogni branch e li preserva quando si cambia tab. Senza di esso, cambiando tab si ricostruisce l'intero sotto-albero.
D: Come si passano oggetti complessi tra le route?
Esistono tre approcci. Primo, passare un ID nel parametro di percorso e recuperare l'oggetto nella schermata di destinazione. Secondo, usare il parametro extra di GoRouter per passare l'oggetto direttamente. Terzo, memorizzare l'oggetto in una soluzione di state management (Riverpod, Bloc) e accedervi dalla destinazione. Il primo approccio è migliore per il deep linking perché l'URL rimane condivisibile.
// 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);
},
),D: Cos'è il callback onEnter in GoRouter 17.5?
Il callback onEnter viene eseguito quando si entra in una route e fornisce accesso sia allo stato della route corrente che a quella successiva. Permette di eseguire logica prima che la route carichi completamente, utile per analytics o pre-caricamento condizionale.
Route type-safe con generazione di codice
GoRouter supporta il routing type-safe attraverso la generazione di codice. Si definiscono classi di dati delle route e si lascia che build_runner generi il codice di navigazione:
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);
}Dopo aver eseguito dart run build_runner build, si naviga con sicurezza a compile-time:
// Type-safe navigation
ProductRoute(id: '123').go(context);Questo elimina gli errori di battitura nei percorsi delle route e assicura che i parametri siano sempre forniti.
Padroneggiare la navigazione Flutter per app in produzione
- GoRouter 17.5 è la soluzione di navigazione raccomandata per le app Flutter che richiedono deep linking, navigazione annidata o supporto web
- Si usa ShellRoute per bottom navigation bar persistenti; si usa StatefulShellRoute per preservare lo stack di navigazione di ogni tab
- Il deep linking specifico per piattaforma (Android App Links, iOS Universal Links) viene configurato separatamente da GoRouter, che gestisce la logica di routing
- I guard di autenticazione vengono implementati con il callback
redirecta livello di router o di singola route - Per gli scenari di colloquio, ci si concentra sulla spiegazione del modello dichiarativo: lo stato dell'app guida la UI, gli URL sono derivati dallo stato, e GoRouter gestisce la traduzione
- Le route type-safe con generazione di codice eliminano gli errori a runtime da percorsi scritti male
- Consultare la documentazione sulla navigazione Flutter e il changelog di GoRouter per le funzionalità specifiche della versione
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in Flutter?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 23 agosto 2026
Tag
Condividi
Articoli correlati

Ottimizzazione delle prestazioni di Flutter nel 2026: Impeller, ricostruzioni e best practice
Come mantenere le app Flutter a 60 o 120 fps costanti nel 2026 con Impeller, ricostruzioni di widget disciplinate, RepaintBoundary e profiling con DevTools.

Flutter Custom RenderObjects nel 2026: Painting Personalizzato e Domande da Colloquio
Una guida approfondita alla pipeline di rendering di Flutter con RenderObjects personalizzati. Scopri quando utilizzare CustomPainter vs RenderBox e preparati per colloqui di livello senior.

React Native vs Flutter 2026: Architettura e Performance a Confronto
Un confronto approfondito tra React Native e Flutter nel 2026: architettura, performance, esperienza di sviluppo e casi d'uso per lo sviluppo mobile cross-platform.