Question #240EasyNavigation & Routing

What is the difference between go and push in go-router?

#route#go_router#navigation#routing

Answer

Overview

In Flutter's

text
go_router
package,
text
go()
and
text
push()
are two different navigation methods that handle the navigation stack differently. Understanding when to use each is crucial for proper app navigation flow.

Key Differences

Feature
text
context.go()
text
context.push()
Stack BehaviorReplaces stackAdds to stack
Navigation TypeDeclarativeImperative
Equivalent to
text
Navigator.pushNamedAndRemoveUntil
text
Navigator.push
Use CaseNavigate and clear historyNavigate and keep history
Back ButtonMay not return to previous screenReturns to previous screen
URL SyncUpdates browser URL (web)Adds to browser history

context.go() - Replace Stack

text
context.go()
replaces the current navigation stack with the screens configured for the destination route. It discards previous routes if the target is not a sub-route.

When to Use go()

  • After successful login (navigate to home)
  • Completing onboarding flow
  • Navigating between main sections
  • When you don't want users to go back

Example

dart
// After successful login
ElevatedButton(
  onPressed: () {
    // User cannot go back to login screen
    context.go('/home');
  },
  child: Text('Login'),
)

context.push() - Add to Stack

text
context.push()
always adds the target route on top of the existing navigation stack, preserving all previous routes.

When to Use push()

  • Opening detail screens
  • Showing modal pages
  • Navigating to forms or settings
  • When users should return to previous screen

Example

dart
// Open product details
ListTile(
  onTap: () {
    // User can go back to product list
    context.push('/product/${product.id}');
  },
  title: Text(product.name),
)

Practical Comparison

Scenario 1: Simple Navigation

dart
// Current stack: ['/', '/home']

// Using go('/details')
context.go('/details');
// Result: ['/', '/details'] - /home is removed

// Using push('/details')
context.push('/details');
// Result: ['/', '/home', '/details'] - /home is kept

Scenario 2: Sub-routes

dart
// Current stack: ['/', '/home']

// Using go('/home/settings') - settings is sub-route of home
context.go('/home/settings');
// Result: ['/', '/home', '/home/settings'] - home is kept

// Using push('/modal') - modal is NOT sub-route of home
context.push('/modal');
// Result: ['/', '/home', '/modal'] - adds to stack

// Using go('/modal') - modal is NOT sub-route of home
context.go('/modal');
// Result: ['/', '/modal'] - home is removed

Complete Example

dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

// Router configuration
final GoRouter router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginScreen(),
    ),
    GoRoute(
      path: '/details/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailsScreen(id: id);
      },
    ),
  ],
);

class HomeScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              // Use push - can go back to home
              context.push('/details/123');
            },
            child: Text('View Details (Push)'),
          ),
          ElevatedButton(
            onPressed: () {
              // Use go - login replaces home in stack
              context.go('/login');
            },
            child: Text('Logout (Go)'),
          ),
        ],
      ),
    );
  }
}

class LoginScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: ElevatedButton(
        onPressed: () {
          // Use go - user cannot go back to login
          context.go('/');
        },
        child: Text('Login'),
      ),
    );
  }
}

Return Values

Both methods can return values, but handle it differently:

dart
// Using push - can await result
final result = await context.push('/form');
if (result == true) {
  print('Form submitted');
}

// Using go - cannot await result directly
context.go('/form');

Best Practices

  • Use

    text
    go()
    for:

    • Authentication flows (login → home)
    • Main navigation tabs/sections
    • Completing multi-step processes
    • When back navigation doesn't make sense
  • Use

    text
    push()
    for:

    • Detail screens
    • Forms and dialogs
    • Settings and preferences
    • Any screen where users should return
  • Avoid mixing both methods unnecessarily as it can confuse navigation behavior

  • Test navigation on both mobile and web to ensure expected behavior

Official Documentation