Basics of Navigation in Flutter

Navigation in Flutter is one of the core aspects of building an interactive and seamless user experience. This guide will introduce you to the basic concepts of navigation in Flutter, starting with the simplest approaches and gradually covering more advanced techniques.

What is Navigation?

In Flutter, navigation refers to moving between different screens or pages of your application. For example, in an e-commerce app, clicking on a product might navigate the user from the product listing page to the product details page.

Types of Navigation in Flutter

Flutter supports several navigation strategies:

  1. Stack-based Navigation (using Navigator and routes)
  2. Named Routes
  3. Navigation with State Management (using packages like provider or riverpod)
  4. Declarative Navigation (using GoRouter or Beamer for advanced setups)

For beginners, we will focus on the first two approaches: Stack-based Navigation and Named Routes.

Setting Up Navigation

1. Stack-based Navigation

The simplest way to navigate between screens is by using the Navigator widget.

Example:

  1. Create two screens:

    import 'package:flutter/material.dart';
    
    // First Screen
    class FirstScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("First Screen")),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SecondScreen()),
                );
              },
              child: Text("Go to Second Screen"),
            ),
          ),
        );
      }
    }
    
    // Second Screen
    class SecondScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Second Screen")),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("Go Back"),
            ),
          ),
        );
      }
    }
    
  2. Run the app and test navigation between the two screens.

2. Named Routes

Named routes are more organized, especially for larger apps with multiple screens. Instead of hardcoding routes, you define them in a Map in your app's main configuration.

Steps:

  1. Define named routes in the MaterialApp:

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          initialRoute: '/',
          routes: {
            '/': (context) => HomeScreen(),
            '/second': (context) => SecondScreen(),
          },
        );
      }
    }
    
  2. Create the screens:

    import 'package:flutter/material.dart';
    
    class HomeScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Home Screen")),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/second');
              },
              child: Text("Go to Second Screen"),
            ),
          ),
        );
      }
    }
    
    class SecondScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Second Screen")),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("Go Back"),
            ),
          ),
        );
      }
    }
    

Now, when you run the app, you can navigate using named routes.

Passing Data Between Screens

Sometimes, you need to pass data from one screen to another. You can do this in two ways:

1. Using Constructor Arguments

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => SecondScreen(data: "Hello from First Screen"),
  ),
);

Then receive the data in the second screen:

class SecondScreen extends StatelessWidget {
  final String data;

  SecondScreen({required this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Second Screen")),
      body: Center(child: Text(data)),
    );
  }
}

2. Using Named Route Arguments

Pass arguments:

Navigator.pushNamed(
  context,
  '/second',
  arguments: "Hello from Home Screen",
);

Retrieve arguments:

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as String;

    return Scaffold(
      appBar: AppBar(title: Text("Second Screen")),
      body: Center(child: Text(args)),
    );
  }
}

Returning Data to Previous Screen

Sometimes, you may need to return data from one screen back to the previous screen. For this, use Navigator.pop.

Example:

  1. Push a new screen and wait for the result:

    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => InputScreen()),
    );
    
    print("Returned Data: $result");
    
  2. Pop the screen with data:

    Navigator.pop(context, "Some Data");
    

Navigation Best Practices

  1. Keep Route Names Centralized: Maintain all route names in a separate file for easy management.
  2. Use Stateful Widgets for Complex Screens: If the screens involve forms or dynamic content, opt for StatefulWidget.
  3. Use Declarative Navigation for Complex Apps: Use packages like GoRouter for apps with dynamic deep linking or nested navigation.

Advanced Concepts (For Later Learning)

  • Bottom Navigation Bar: Learn to switch between tabs.
  • Drawer Navigation: Add a side menu for navigation.
  • Deep Linking: Enable opening specific pages from external links.
  • Navigation with State Management: Use provider or riverpod to control navigation.

Summary

Navigation is a crucial part of any Flutter application. Start with stack-based and named route navigation to build your foundation. As your app grows, explore advanced navigation techniques and tools for better performance and usability.