Question #307MediumNavigation & Routing

What is the difference between Navigator and Router in Flutter?

#flutter#navigation#routing#navigator#router

Answer

Overview

Navigator and Router are two different approaches to handling navigation in Flutter. Navigator represents the traditional imperative navigation (Navigator 1.0), while Router provides a declarative navigation system (Navigator 2.0). Understanding both is essential for choosing the right navigation strategy.

Key Differences

FeatureNavigator (1.0)Router (2.0)
ApproachImperativeDeclarative
Navigation StyleCommand-based (push/pop)State-based
Stack ControlLimited (top only)Full control
Deep LinkingManual implementationBuilt-in support
URL SynchronizationNot supportedAutomatic
Web SupportBasicExcellent
ComplexitySimpleMore complex
BoilerplateMinimalHigher
State RestorationLimitedFull support
Use CaseSimple appsComplex apps, web

Navigator (Imperative Navigation)

How It Works

Navigator uses an imperative approach where you explicitly tell Flutter what to do using methods like

text
push()
and
text
pop()
.

Basic Example

dart
// Push a new screen
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => DetailScreen()),
);

// Pop current screen
Navigator.pop(context);

// Push with named routes
Navigator.pushNamed(context, '/details');

// Replace current route
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => HomeScreen()),
);

// Clear stack and push
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => LoginScreen()),
  (route) => false,
);

Named Routes Setup

dart
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/details': (context) => DetailScreen(),
        '/settings': (context) => SettingsScreen(),
      },
    );
  }
}

Advantages of Navigator

  • Simple to learn and use
  • Minimal boilerplate code
  • Quick setup for basic apps
  • Straightforward for linear navigation flows

Limitations of Navigator

  • Limited stack control - can only add to top or remove from top
  • No URL synchronization for web
  • Manual deep linking implementation required
  • Harder to test navigation logic
  • Tightly coupled to UI code

Router (Declarative Navigation)

How It Works

Router uses a declarative approach where navigation is a function of app state. You describe what the navigation stack should look like, and Flutter updates it accordingly.

Core Components

  1. RouterDelegate - Builds the Navigator widget based on app state
  2. RouteInformationParser - Converts URLs to app state
  3. RouteInformationProvider - Provides route information to the router

Basic Example

dart
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
  
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  bool _showDetails = false;
  String? _selectedId;

  // App state getters
  bool get showDetails => _showDetails;
  String? get selectedId => _selectedId;

  // Methods to update state
  void showHomePage() {
    _showDetails = false;
    notifyListeners();
  }

  void showDetailsPage(String id) {
    _selectedId = id;
    _showDetails = true;
    notifyListeners();
  }

  
  Widget build(BuildContext context) {
    // Navigation stack is built declaratively based on state
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: ValueKey('home'),
          child: HomeScreen(
            onItemTapped: (id) => showDetailsPage(id),
          ),
        ),
        if (_showDetails)
          MaterialPage(
            key: ValueKey('details-$_selectedId'),
            child: DetailScreen(id: _selectedId!),
          ),
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }
        // Update state when page is popped
        _showDetails = false;
        notifyListeners();
        return true;
      },
    );
  }

  
  AppRoutePath get currentConfiguration {
    if (_showDetails) {
      return AppRoutePath.details(_selectedId!);
    }
    return AppRoutePath.home();
  }

  
  Future<void> setNewRoutePath(AppRoutePath path) async {
    if (path.isDetailsPage) {
      showDetailsPage(path.id!);
    } else {
      showHomePage();
    }
  }
}

class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> {
  
  Future<AppRoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location!);
    
    // Handle '/'
    if (uri.pathSegments.isEmpty) {
      return AppRoutePath.home();
    }
    
    // Handle '/details/:id'
    if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'details') {
      return AppRoutePath.details(uri.pathSegments[1]);
    }
    
    return AppRoutePath.home();
  }

  
  RouteInformation restoreRouteInformation(AppRoutePath path) {
    if (path.isDetailsPage) {
      return RouteInformation(location: '/details/${path.id}');
    }
    return RouteInformation(location: '/');
  }
}

// Route path model
class AppRoutePath {
  final String? id;
  final bool isDetailsPage;

  AppRoutePath.home()
      : id = null,
        isDetailsPage = false;

  AppRoutePath.details(this.id) : isDetailsPage = true;
}

// App setup
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: AppRouterDelegate(),
      routeInformationParser: AppRouteInformationParser(),
    );
  }
}

Advantages of Router

  • Full stack control - manipulate entire navigation stack
  • Built-in deep linking support
  • URL synchronization for web apps
  • State restoration automatically handled
  • Better testability - navigation is pure function of state
  • Declarative - easier to reason about complex flows
  • Web-friendly - proper browser back/forward support

Limitations of Router

  • Steeper learning curve
  • More boilerplate code
  • Complex setup for simple apps
  • Verbose implementation

Comparison Example

Scenario: Navigate to details page and back

Navigator (Imperative):

dart
// In HomeScreen
ListTile(
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DetailScreen(id: '123'),
      ),
    );
  },
  title: Text('Item'),
)

// In DetailScreen
ElevatedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('Back'),
)

Router (Declarative):

dart
// Update state to show details
void showDetailsPage(String id) {
  _selectedId = id;
  _showDetails = true;
  notifyListeners(); // Navigator rebuilds automatically
}

// Update state to go back
void showHomePage() {
  _showDetails = false;
  notifyListeners(); // Navigator rebuilds automatically
}

When to Use Each

Use Navigator When:

  • Building simple mobile apps
  • Need quick prototyping
  • Navigation is straightforward and linear
  • No deep linking required
  • Mobile-only application
  • Team prefers simplicity over features

Use Router When:

  • Building web applications
  • Need deep linking support
  • Require URL synchronization
  • Complex authentication flows
  • Need state restoration
  • Want full control over navigation stack
  • Building production apps with complex navigation

Hybrid Approach

You can also use packages like

text
go_router
that build on Router but provide a simpler API:

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

Best Practices

  • Start with Navigator for learning and simple apps
  • Use Router (or go_router) for production web apps
  • Don't mix imperative and declarative patterns unnecessarily
  • Consider packages like go_router for easier Router usage
  • Test navigation thoroughly in both approaches

Official Documentation