flutter
/

Flutter Navigator Tutorial for Beginners

Last Sync: Today

On this page

9
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter Navigator Tutorial for Beginners

What is Navigator in Flutter?

Navigator is a widget that manages a stack of Route objects. In simple terms, it's the engine that handles moving between screens (or pages) in your app. When you push a new route, it goes on top of the stack; when you pop, you return to the previous route. Flutter provides a powerful and flexible navigation system that works on iOS, Android, and the web.

Basic Navigation: push and pop

The simplest way to navigate to a new screen is by using Navigator.push() and Navigator.pop(). You wrap the new screen in a MaterialPageRoute which provides a platform‑adaptive transition.

DARTRead-only
1
// On first screen (e.g., HomeScreen)
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SecondScreen()),
    );
  },
  child: Text('Go to Second Screen'),
);

// Inside SecondScreen, to go back:
ElevatedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('Back'),
);

When you call pop, the current screen is removed from the stack and the previous screen becomes visible again.

Named Routes

For larger apps, managing direct widget references can become messy. Named routes allow you to define route names in the MaterialApp and navigate using those names.

DARTRead-only
1
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/details': (context) => DetailsScreen(),
    '/settings': (context) => SettingsScreen(),
  },
)

// Navigate using name
Navigator.pushNamed(context, '/details');

// Pop back
Navigator.pop(context);

This approach centralizes your route definitions and makes navigation more readable.

Passing Data Between Screens

Often you need to send data to the new screen, or receive data back. With named routes, you can pass arguments using the arguments parameter.

DARTRead-only
1
// Sending data
Navigator.pushNamed(
  context,
  '/details',
  arguments: {'id': 42, 'name': 'Product X'},
);

// Receiving data in the destination screen
class DetailsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
    return Scaffold(
      appBar: AppBar(title: Text(args['name'])),
      body: Center(child: Text('Product ID: ${args['id']}')),
    );
  }
}

Returning Data from a Screen

Sometimes you want a result back from the screen you pushed. You can use Navigator.pop(context, result) and await the push call.

DARTRead-only
1
// Pushing and awaiting result
final result = await Navigator.pushNamed(context, '/picker');
print('User selected: $result');

// In the picker screen, when user makes a selection
Navigator.pop(context, 'Apple');

onGenerateRoute and Route Settings

For dynamic route handling (e.g., parsing URLs, handling unknown routes), you can use onGenerateRoute. It gives you full control over the route creation based on the RouteSettings.

DARTRead-only
1
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/details') {
      final args = settings.arguments as Map;
      return MaterialPageRoute(
        builder: (context) => DetailsScreen(id: args['id']),
      );
    }
    // Fallback for unknown routes
    return MaterialPageRoute(builder: (context) => NotFoundScreen());
  },
)

Common Mistakes Beginners Make

  • Not wrapping the app in a MaterialApp or CupertinoApp: Navigator relies on the context provided by these widgets. Without them, Navigator.of(context) will fail.
  • Using context incorrectly: context must be from a widget that is a descendant of the Navigator. Usually, the context from the current screen's build method is fine.
  • Forgetting to handle the back button: On Android, the physical back button automatically pops the route. However, if you want to intercept or disable it, you need WillPopScope.
  • Passing arguments without type safety: Using Map for arguments is flexible but error‑prone. Consider using strongly‑typed objects or arguments as a custom class.
  • Not awaiting pop when expecting a result: If you need data back, always await the push call and handle the case where the user cancels (pop returns null).

Key Points to Remember

  • Navigator manages a stack of routes. push adds a route, pop removes it.
  • Use MaterialPageRoute for platform‑adaptive transitions.
  • Named routes (routes map) simplify navigation in larger apps.
  • Pass data using the arguments parameter and retrieve it with ModalRoute.of(context)!.settings.arguments.
  • Return data by passing a value to pop and awaiting the push call.
  • onGenerateRoute gives you full control for dynamic routes and error handling.
  • Always ensure your BuildContext is valid for navigation (usually inside the widget tree of a screen).

Common Interview Questions

  1. How does Navigator work internally?
  2. What is the difference between Navigator.push and Navigator.pushNamed?
  3. How would you pass data from a second screen back to the first?
  4. Explain the purpose of onGenerateRoute and when you would use it instead of the routes map.
  5. How can you handle deep links in Flutter using Navigator?
  6. What is WillPopScope and when would you use it?

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/details': (context) => DetailsScreen(),
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Welcome!'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.pushNamed(context, '/details', arguments: 'Hello from Home');
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Result: $result')),
                );
              },
              child: Text('Go to Details'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final message = ModalRoute.of(context)!.settings.arguments as String;
    return Scaffold(
      appBar: AppBar(title: Text('Details')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Message received: $message'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => Navigator.pop(context, 'Data from Details'),
              child: Text('Go back with data'),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

How do you navigate to a new screen and remove the current one from the stack?

A
Navigator.push(context, route)
B
Navigator.pushReplacement(context, route)
C
Navigator.pop(context)
D
Navigator.pushAndRemoveUntil(context, route, (route) => false)
Q2
of 3

How do you retrieve arguments passed to a named route?

A
ModalRoute.of(context)!.settings.arguments
B
Navigator.of(context).arguments
C
RouteSettings.of(context).arguments
D
MediaQuery.of(context).arguments
Q3
of 3

Which widget allows you to intercept the Android back button?

A
BackButtonInterceptor
B
WillPopScope
C
PopScope
D
NavigatorObserver

Previous

flutter mediaquery

Next

flutter theme

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.