Navigation and Routing: Navigator 2.0 and go_router
Navigation is the skeletal structure of a Flutter application. While Flutter initially launched with a simple "Imperative" stack-based API (Navigator 1.0), it has since matured into a fully "Declarative" system (Navigator 2.0) designed to handle the sophisticated requirements of Web URL synchronization and mobile Deep Linking.
1. Navigator 1.0: Imperative Stack Manipulation
In the original API, navigation was treated like a physical stack of papers. You manually added or removed items from the top:
Navigator.push: Commands the app to move to a specific instance of a page.Navigator.pop: Removes the current top-most page and optionally returns a result to the caller.Navigator.pushAndRemoveUntil: A specialized command to purge the entire history stack and reset the app onto a fresh page—common after a user logs in or out.
The Architectural Wall: Navigator 1.0 cannot easily "understand" the application state. It makes syncing with a browser's address bar or handling a "Nested Navigation" (a list inside a list) extremely complex.
2. Navigator 2.0: Declarative and State-Driven
Navigator 2.0 treats the navigation stack as a function of the application state. Instead of saying "Push Page B," you update your state, and the Router automatically adjusts the stack to reflect that state.
Because the underlying Navigator 2.0 API is notoriously verbose, the industry has standardized on go_router, a high-performance routing library maintained by the Flutter team.
3. Implementation with go_router
Path-Based Configuration
go_router uses URL-like paths, allowing for easy parameter extraction:
GoRoute(
path: '/user/:userId',
builder: (context, state) => UserProfile(id: state.pathParameters['userId']),
)
The "Push" vs. "Go" Strategy
context.push('/settings'): Layering. Places the settings page on top of the current stack. The user can "Back" out of it.context.go('/dashboard'): Replacement. Changes the underlying routing state. This is the preferred method for high-level transitions like switching between bottom navigation tabs.
4. ShellRoute: Persistent Shells
In modern apps, you often have a Bottom Navigation Bar that persists while the internal content changes.
ShellRoute is the specialized tool for this. It provides a common "Shell" (like a Scaffold with a bottom bar) and nests sub-routes inside it. This ensures that the navigation bar stays perfectly still while the pages inside it animate in and out, creating a premium, native-feeling user experience.
5. Middleware: Auth Guards and Redirects
Routing logic often involves security. Use the redirect property in go_router to implement "Auth Guards" globally:
final router = GoRouter(
redirect: (context, state) {
bool loggedIn = authService.isLoggedIn;
// If user is not logged in and is not currently on the login page...
if (!loggedIn && state.matchedLocation != '/login') {
return '/login'; // Force them to the login screen
}
return null; // Proceed to the original destination
},
// ...
);
6. Deep Linking
With go_router, deep links are handled natively. By configuring App Links (Android) or Universal Links (iOS), a URL opened from an email or a browser (e.g., brand.com/promo/summer24) will instantly launch the app and navigate to the exact sub-page defined in your routes configuration.