Navigating between screens is a fundamental aspect of any Flutter application. With the advent of the GoRouter package, managing routes has become more streamlined and declarative. However, understanding the nuances between different navigation methods is crucial for maintaining a predictable navigation stack. In this article, we'll delve into the differences between context.push
and context.go
in GoRouter, focusing on their behaviors in various routing scenarios with concrete examples.
π Table of Contents
π Introduction to GoRouter
GoRouter is a powerful routing package for Flutter that simplifies navigation and deep linking. It leverages Flutter's Navigator 2.0 API, providing a declarative approach to defining routes and handling navigation actions. Two primary methods for navigation in GoRouter are context.push
and context.go
. Understanding their differences is essential for effective route management.
context.push
vs context.go
Both context.push
and context.go
are used to navigate between routes, but they behave differently depending on the routing structure and the desired navigation behavior.
context.push
- Purpose: Adds a new route on top of the current navigation stack.
-
Equivalent To:
Navigator.push
in Flutter's native Navigator. - Use Case: When you want to navigate to a new screen without removing the current one, allowing the user to return to the previous screen.
context.go
- Purpose: Replaces the current stack of screens with the screens configured for the destination route.
-
Equivalent To:
Navigator.pushNamedAndRemoveUntil
with a predicate that removes all existing routes (e.g.,(Route<dynamic> route) => false
). - Use Case: When you want to navigate to a new screen and remove all previous routes, such as navigating to a home screen after a successful login or redirecting to a splash screen.
π Key Differences
Aspect | context.push |
context.go |
---|---|---|
Stack Behavior | Adds to the existing stack | Replaces the current stack with the destination route stack |
Navigation Depth | Maintains history for back navigation | Clears history unless navigating to a nested route, which preserves parent routes |
Use Cases | Navigating to detail screens, modals | Redirecting after authentication, resetting navigation, navigating to a new root screen |
Equivalent Methods | Navigator.push |
Navigator.pushNamedAndRemoveUntil with a predicate to remove all previous routes |
π―ββοΈ Behavior with Sibling Routes
Scenario: You have two sibling routes, Login
and Profile
, both defined at the same hierarchical level.
Using context.push
-
Initial Stack:
[Login]
-
Action:
context.push('/profile')
-
Resulting Stack:
[Login, Profile]
-
Behavior:
Profile
is added on top ofLogin
, allowing the user to navigate back toLogin
.
Using context.go
-
Initial Stack:
[Login]
-
Action:
context.go('/profile')
-
Resulting Stack:
[Profile]
-
Behavior: The entire stack is replaced with
Profile
, removingLogin
from the history.
π Behavior with Nested Routes
Scenario: Route Settings
is a subroute (child) of route Home
.
Using context.push
-
Initial Stack:
[Home]
-
Action:
context.push('/home/settings')
-
Resulting Stack:
[Home, Settings]
-
Behavior:
Settings
is added as a child ofHome
, maintaining the parent-child relationship. The user can navigate back toHome
.
Using context.go
-
Initial Stack:
[Home]
-
Action:
context.go('/home/settings')
-
Resulting Stack:
[Home, Settings]
-
Behavior: Since
Settings
is a subroute ofHome
,Home
remains in the stack, andSettings
is added on top. The overall stack structure preserves the parent-child relationship, allowing back navigation toHome
.
π Practical Examples
Let's solidify our understanding with practical code examples using concrete route names like /login
, /home
, /home/settings
, and /profile
.
π Route Configuration
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter(
initialLocation: '/login',
routes: [
GoRoute(
path: '/login',
name: 'Login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/home',
name: 'Home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'settings',
name: 'Settings',
builder: (context, state) => const SettingsScreen(),
),
],
),
GoRoute(
path: '/profile',
name: 'Profile',
builder: (context, state) => const ProfileScreen(),
),
],
);
π οΈ LoginScreen with Navigation Buttons
class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Simulate a successful login
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate to Home after login
context.go('/home');
},
child: const Text('Login and Go to Home'),
),
),
);
}
}
π HomeScreen with Navigation Buttons
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Buttons to navigate to Settings and Profile
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.push('/home/settings'),
child: const Text('Push to Settings (Nested)'),
),
ElevatedButton(
onPressed: () => context.go('/profile'),
child: const Text('Go to Profile (Sibling)'),
),
],
),
),
);
}
}
βοΈ SettingsScreen with Back Navigation
class SettingsScreen extends StatelessWidget {
const SettingsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Button to navigate back to Home
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Center(
child: ElevatedButton(
onPressed: () => context.pop(),
child: const Text('Go Back to Home'),
),
),
);
}
}
π€ ProfileScreen with Back Navigation
class ProfileScreen extends StatelessWidget {
const ProfileScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Button to navigate back to Home (if possible)
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Center(
child: ElevatedButton(
onPressed: () => context.pop(),
child: const Text('Go Back'),
),
),
);
}
}
π Expected Behaviors
-
Login and Go to Home
-
Method:
context.go('/home')
-
Stack Transition:
[Login]
β[Home]
-
Back Navigation: π« Disabled (cannot go back to
Login
)
-
Method:
-
Push to Settings (
/home/settings
)-
Method:
context.push('/home/settings')
-
Stack Transition:
[Home]
β[Home, Settings]
-
Back Navigation: π Enabled (can go back to
Home
)
-
Method:
-
Go to Profile (
/profile
)-
Method:
context.go('/profile')
-
Stack Transition:
[Home]
β[Profile]
-
Back Navigation: π« Disabled (cannot go back to
Home
)
-
Method:
-
Go Back from Settings
-
Method:
context.pop()
-
Stack Transition:
[Home, Settings]
β[Home]
-
Back Navigation: π Enabled (can go back to
Home
)
-
Method:
β Conclusion
Understanding the distinction between context.push
and context.go
in GoRouter is essential for effective navigation management in Flutter applications:
Use
context.push
when you want to add a new route on top of the current stack, preserving the ability to navigate back. This is ideal for scenarios like navigating to detail screens or opening modals where the previous screen remains relevant.Use
context.go
when you intend to replace the current navigation stack with a new route. This is particularly useful for actions like redirecting users after authentication, resetting navigation history, or navigating to a new root screen where returning to previous screens is not desired.
Happy Coding! π
Top comments (0)