Flutter Learning Roadmap

12 min

12 min

Sahaj Rana

Published on Apr 29, 2024

Navigation in Flutter: Learn how to navigate effectively in Flutter apps with tabs, screens, data transfer, and drawers.

Learn how to navigate efficiently in Flutter apps with this comprehensive guide. From adding tabs to passing data between screens, explore essential techniques and best practices. Master Flutter's navigation system to build dynamic user interfaces effortlessly.
Learn how to navigate efficiently in Flutter apps with this comprehensive guide. From adding tabs to passing data between screens, explore essential techniques and best practices. Master Flutter's navigation system to build dynamic user interfaces effortlessly.

Introduction

Welcome to the world of Flutter navigation! In this blog, In this segment, we'll explore the fundamentals of Flutter navigation, covering tab-based navigation, screen transitions, data passing, and drawer implementation. Whether you're a novice or a seasoned developer, mastering these navigation techniques is essential for building intuitive and user-friendly Flutter apps. Flutter's navigation system is a cornerstone of app development, enabling developers to create dynamic and intuitive interfaces.

Why is Flutter navigation important?

  • How does Flutter enable effective navigation in apps?

  • What are the key components of Flutter's navigation system?

  • How can developers implement tab-based navigation in Flutter apps?

Facts About Navigation in Flutter

  • Navigator Widget: Flutter uses the Navigator widget to manage routes and transitions between screens.

  • Tab Navigation: Tab navigation in Flutter is achieved using TabBar and TabBarView widgets, providing easy access to different sections of the app.

  • Data Passing: Flutter facilitates data passing between screens via constructor and route arguments, ensuring smooth communication within the app.

This blog is part of our Flutter learning roadmap series, following our previous exploration of "Assets and Images in Flutter". If you missed it, be sure to check out the blog to gain insights into managing assets and images effectively in your Flutter projects.

Stay tuned as we delve deeper into each navigation aspect, providing practical insights and best practices to elevate your Flutter development skills.

Navigation

Navigation is essential for building engaging and intuitive Flutter apps, enabling users to seamlessly move between different screens and sections.

Using the Navigator

The Navigator class in Flutter manages a stack of Route objects and allows for navigation between different screens or routes within an app. To navigate to a new screen, access the Navigator through the route's BuildContext and utilize imperative methods like push() or pop():

onPressed: () {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => const SongScreen(song: song),
    ),
  );
},
child: Text(song.name),

Because the Navigator maintains a stack of Route objects representing the navigation history, the push() method requires a Route object. The MaterialPageRoute class, a subclass of Route, specifies transition animations, particularly for Material Design.

Using named routes

Named routes offer a way to define and navigate to specific screens using unique identifiers rather than directly referencing the widget class. While useful for simpler navigation (Navigator) and deep linking requirements (MaterialApp.routes).
Routes specified here are called named routes. For a complete example:

routes: {
  '/': (context) => HomeScreen(),
  '/details': (context) => DetailScreen(),
},

Using the Router

For advanced navigation needs, like web apps with direct links or apps with multiple Navigator widgets, consider using a routing package like go_router. Configure it through MaterialApp's router constructor:

MaterialApp.router(
  routerConfig: GoRouter(    // Configuration details
  )
);

Using Router and Navigator together

The Router and Navigator complement each other. Navigate using the Router API or directly through the Navigator. Routes from the Router are page-backed, while those from Navigator.push() are pageless. Removing a page-backed route also removes subsequent pageless routes until the next page-backed one.

Adding Tabs to Your App

Tabs are a familiar feature in apps adhering to Material Design principles. Flutter simplifies tab layout creation through its material library.

Here's how to create a tabbed layout:

  1. Set up a TabController.

  2. Define the tabs.

  3. Populate each tab with content.

Create a TabController

To enable tab functionality, synchronize the selected tab with its corresponding content using a TabController. You can create a TabController manually or opt for the easier route by utilizing the DefaultTabController widget, which automatically generates and shares a TabController with all descendant widgets.

return MaterialApp(
  home: DefaultTabController(
    length: 3,
    child: Scaffold(),
  ),
);

Create the tabs

To display content when a tab is selected, utilize the TabBar widget. In this scenario, construct a TabBar with three Tab widgets and nest it within an AppBar.

return MaterialApp(
  home: DefaultTabController(
    length: 3,
    child: Scaffold(
      appBar: AppBar(
        bottom: const TabBar(
          tabs: [
            Tab(icon: Icon(Icons.directions_car)),
            Tab(icon: Icon(Icons.directions_transit)),
            Tab(icon: Icon(Icons.directions_bike)),
          ],
        ),
      ),
    ),
  ),
);

By default, the TabBar searches for the nearest DefaultTabController in the widget tree. If you're manually creating a TabController, ensure to pass it to the TabBar.

Create content for each tab

To display content corresponding to each tab selection, incorporate the TabBarView widget.

body: const TabBarView(
  children: [
    Icon(Icons.directions_car),
    Icon(Icons.directions_transit),
    Icon(Icons.directions_bike),
  ],
),

Interactive example

Navigate to a New Screen and Back

In Flutter, routes represent screens or pages in an app. Using the Navigator widget, you can navigate between routes. This recipe focuses on navigating between two routes using Navigator.push() to move to the second route and Navigator.pop() to return to the first route.

Create two routes

Let's set up the visual structure by creating two routes. Each route will consist of a single button. Tapping the button on the first route will navigate to the second route, and tapping the button on the second route will return to the first route.

class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('First Route'),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Route'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}

Navigate to the second route using Navigator.push()

To transition to a new route, utilize the Navigator.push() method. This method adds a Route to the stack of routes managed by the Navigator. You can create your own Route or opt for a MaterialPageRoute, which facilitates seamless transitions to the new route with platform-specific animations.

In the build() method of the FirstRoute widget, update the onPressed() callback accordingly.

// Within the `FirstRoute` widget
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const SecondRoute()),
  );
}

Return to the first route using Navigator.pop()

To return to the first route from the second, use Navigator.pop(). This method removes the current route from the stack. Update the onPressed() callback in the SecondRoute widget to achieve this.

// Within the SecondRoute widget
onPressed: () {
  Navigator.pop(context);
}

Navigation with CupertinoPageRoute

In Flutter, besides Material components, you also have access to Cupertino (iOS-style) widgets. You can use CupertinoPageRoute for iOS-style transitions between screens. Replace MaterialApp with CupertinoApp, Scaffold with CupertinoPageScaffold, and ElevatedButton with CupertinoButton to align with iOS design.

Interactive example

Sending Data to a New Screen

Let's explore how to navigate to a new screen while passing data to it. Consider a scenario where you want to pass information about a tapped item.

Here's how you can achieve this:

  1. Define a todo class to represent each todo item.

  2. Display a list of todos.

  3. Create a detailed screen capable of showing information about a todo.

  4. Navigate to the detail screen and pass data about the selected todo.

Define a todo class

To represent todos, create a class containing two pieces of data: title and description.

class Todo {
  final String title;
  final String description;

  const Todo(this.title, this.description);
}

Create a list of todos

Generate 20 todos and display them using a ListView.

final todos = List.generate(
  20,
  (i) => Todo(
    'Todo $i',
    'A description of what needs to be done for Todo $i',
  ),
);

ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
    );
  },
);

Create a Todo screen to display the list

Create a StatelessWidget called TodosScreen. Require the list of todos within the widget's scope and use ListView.builder to display the list.

class TodosScreen extends StatelessWidget {
  // Requiring the list of todos.
  const TodosScreen({super.key, required this.todos});
  final List<Todo> todos;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todos'),
      ),       //passing in the ListView.builder
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
          );
        },
      ),
    );
  }
}

Create a DetailScreen to display information

Create a StatelessWidget called DetailScreen. Require a Todo in the constructor and use it to build the UI.

class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});
  final Todo todo;  // Declare a field that holds the Todo.
  @override
  Widget build(BuildContext context) {
    return Scaffold(    // Use the Todo to create the UI.
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}

Navigate and pass data to the detail screen

Implement navigation to DetailScreen when a user taps a todo in the list. Pass the selected todo to DetailScreen using Navigator.push().

body: ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DetailScreen(todo: todos[index]),
          ),
        );
      },
    );
  },
);

Interactive example


Return data from a screen

When you need to return data from a new screen, you can utilize the Navigator.pop() method. Here's how you can achieve this:

  1. Define the home screen

  2. Add a button that launches the selection screen

  3. Show the selection screen with two buttons

  4. When a button is tapped, close the selection screen

  5. Show a snackbar on the home screen with the selection

Define the home screen

The home screen displays a button that launches the selection screen.

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Returning Data Demo'),
      ),
      body: const Center(
        child: SelectionButton(),
      ),
    );
  }
}

Add a button that launches the selection screen

Create the SelectionButton, which launches the SelectionScreen when tapped and waits for the SelectionScreen to return a result.

class SelectionButton extends StatefulWidget {
  const SelectionButton({super.key});

  @override
  State<SelectionButton> createState() => _SelectionButtonState();
}

class _SelectionButtonState extends State<SelectionButton> {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: const Text('Pick an option, any option!'),
    );
  }
  Future<void> _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute(builder: (context) => const SelectionScreen()),
    );
  }
}

Show the selection screen with two buttons:

Create the SelectionScreen, which displays two buttons. Tapping a button closes the selection screen and informs the home screen of the selection.

class SelectionScreen extends StatelessWidget {
  const SelectionScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context, 'Yep!');
              },
              child: const Text('Yep!'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context, 'Nope.');
              },
              child: const Text('Nope.'),
            )
          ],
        ),
      ),
    );
  }
}

When a button is tapped, close the selection screen:

Update the onPressed() callback for both buttons to close the screen and return the selected option. Use the Navigator.pop() method, which accepts an optional second argument called result.

// Close the screen and return "Yep!" as the result.
ElevatedButton(
  onPressed: () {
    Navigator.pop(context, 'Yep!');
  },
  child: const Text('Yep!'),
)

// Close the screen and return "Nope." as the result.
ElevatedButton(
  onPressed: () {
    // Close the screen and return "Nope." as the result.
    Navigator.pop(context, 'Nope.');
  },
  child: const Text('Nope.'),
)

Show a snackbar on the home screen with the selection:

After obtaining the result from the selection screen, display it using a snackbar on the home screen.

final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SelectionScreen()),
);

ScaffoldMessenger.of(context)
  ..removeCurrentSnackBar()
  ..showSnackBar(SnackBar(content: Text('$result')));

Interactive example

Adding a Drawer to a Screen

In Material Design apps, navigation options include tabs and drawers. When space is limited, drawers serve as a practical alternative.

In Flutter, use the Drawer widget along with a Scaffold to implement a layout with a Material Design drawer. Here's how:

  1. Create a Scaffold.

  2. Add a drawer.

  3. Populate the drawer with items.

  4. Programmatically close the drawer.

Create a Scaffold

To add a drawer to your app, use a Scaffold widget. It provides a consistent layout and supports Material Design components. Here's a basic setup:

Scaffold(
  appBar: AppBar(
    title: const Text('AppBar without hamburger button'),
  ),
  drawer: // Add a Drawer here in the next step.
);

Add a drawer

Let's integrate a drawer into the Scaffold. For a Material Design-compliant drawer, utilize the Drawer widget from the material library. Here's how:

Scaffold(
  appBar: AppBar(
    title: const Text('AppBar with hamburger button'),
  ),
  drawer: Drawer(
    child: // Populate the Drawer in the next step.
  ),
);

Populate the drawer with items

Let's fill the drawer with content. Utilize a ListView for this purpose. Though you could use a Column widget, ListView is preferable as it enables scrolling if the content exceeds the screen space.

Include a DrawerHeader and two ListTile widgets within the ListView. Here's how:

Drawer(
  // Add a ListView to the drawer. This ensures the user can scroll
  // through the options in the drawer if there isn't enough vertical
  // space to fit everything.
  child: ListView(
    // Important: Remove any padding from the ListView.
    padding: EdgeInsets.zero,
    children: [
      const DrawerHeader(
        decoration: BoxDecoration(
          color: Colors.blue,
        ),
        child: Text('Drawer Header'),
      ),
      ListTile(
        title: const Text('Item 1'),
        onTap: () {
          // Update the state of the app.
          // ...
        },
      ),
      ListTile(
        title: const Text('Item 2'),
        onTap: () {
          // Update the state of the app.
          // ...
        },
      ),
    ],
  ),
);

Close the drawer programmatically

To close the drawer after a user taps an item, utilize the Navigator. When the drawer is opened, Flutter adds it to the navigation stack. Therefore, to close the drawer, call Navigator.pop(context).

Interactive example

Download Our Flutter-based App Builder "Blup"

Experience the Benefits of BLUP - Your Low-Code Flutter App Builder!

Start Building with Blup - Blup's Role in App Development

Unlock the Potential:

Efficiency: Build Flutter apps faster with our intuitive drag-and-drop interface, reducing development time and effort.

Cost-Effective: Save on development costs with BLUP's low-code capabilities, eliminating the need for extensive coding expertise.

Scalability: Seamlessly scale your apps as your business grows, with BLUP's flexible architecture and easy customization options.

Flutter Learning Resources

Explore these recommended resources to deepen your understanding of Flutter navigation:

  1. Official Documentation: Comprehensive guidance and code samples.

  2. Tutorials: Step-by-step guidance on navigation patterns.

  3. Blogs: Insights and tips from Flutter developers.

  4. Videos: Visual tutorials and coding sessions.

  5. Community Forums: Seek help and share knowledge.

Conclusion

As we wrap up Part 1 of our Flutter learning roadmap series, it's essential to reflect on the key concepts we've covered. Navigating through Flutter apps using tabs, screens, data transfer, and drawers is fundamental for creating engaging user experiences.

This conclusion marks the end of Part 1 in our Flutter learning roadmap series, following our previous blog on "Assets and Images in Flutter." Navigation is a critical aspect of Flutter app development, influencing user engagement and satisfaction. By mastering navigation fundamentals, you lay a solid foundation for building intuitive and user-friendly Flutter apps.

Stay tuned for more insightful content as we continue our journey through the Flutter learning roadmap series, exploring advanced topics and techniques to elevate your Flutter development skills.

Top Blogs

Follow us on

Follow us on