Navigation in Flutter

Key takeaways:

  • Flutter uses a robust routing mechanism for navigation between screens, defined via the MaterialApp widget. Named routes streamline screen transitions, enabling efficient page management.

  • Flutter allows seamless data transfer between pages using Navigator.pushNamed and ModalRoute.of(context).settings.arguments, facilitating personalized and interactive UI experiences.

  • Navigator.push and Navigator.pop manage the navigation stack, providing granular control over screen transitions, including returning to previous pages.

  • Building multi-page applications involves defining routes, structuring navigation logic, and using widgets like Scaffold for UI consistency, as shown in the complete example.

If you're comfortable building single-page applications and want to expand your Flutter app by adding multiple pages, the transition may seem easy. However, the real challenge is how to efficiently connect those pages through smooth navigation. In this answer, we will explore how to navigate between pages using simple yet powerful techniques in Flutter, ensuring a seamless user experience.

Note: We offer various courses if you aim to continue your journey in learning Flutter!

Flutter

Flutter is a UI development toolkit primarily used to build applications for various platforms using a single codebase. It is completely open-source and is a product of Google.

Simply put, this means that developers can write the code once and use it on different platforms without having to start from scratch for each one.

Flutter in a few words
Flutter in a few words

Note: Before proceeding with the answer, ensure that your Flutter setup is complete.

Navigation

Navigation refers to moving from one place to another. In the context of websites or mobile apps, we mean moving from one page (or screen) to another.

When we interact with links or buttons on an application, we are basically navigating through different pages or screens. For example, when we click on the "Home" button, we navigate to the homepage; when we click on "About us," the "About us" page opens, and so on.

Navigating between pages
Navigating between pages

Routing

In Flutter, navigation is achieved using a technique called routing. Routing defines routes Routes enable users to easily move between different screens for each screen in the app and then uses those routes to navigate between them. For instance, "/home" could be a route for the home page. When we perform an action that triggers a navigation event, the application opens that screen for us, defined by that route.

Key concepts in navigation

Let's dig in to the concepts of navigation, one by one.

Defining routes

The first step in navigating between pages is to define your routes. In Flutter, you define routes in the MaterialApp widget using the routes parameter. Each route corresponds to a specific widget (screen) that represents a page in your app.

For example:

  • /login: This could represent your login page.

  • /home: This could represent your home page.

Once the routes are defined, they serve as the pathways your app will follow when transitioning between pages.

Navigating to a new screen

Once you have your routes defined, you can move from one page to another using Navigator.pushNamed. This method takes in the current context and the route name you wish to navigate to.

For example, when a user logs in successfully, the app might navigate from the login screen to the home screen.

Returning from a screen

In Flutter, navigating back to the previous screen is simple with Navigator.pop. This removes the current screen from the navigation stack and returns the user to the last screen they were on. Think of this as hitting the "back" button in your browser or app.

Passing data between screens

Often, you’ll need to pass data between screens. For instance, when a user logs in, you may want to send their username from the login page to the home page, where it will be displayed. You can do this by passing arguments during navigation using Navigator.pushNamed, and retrieving them on the new screen using ModalRoute.of(context).settings.arguments.

This is particularly useful for personalizing pages (like displaying a username) or transferring specific information (like a product ID in an e-commerce app).

Coding example

Suppose we have two pages and want to navigate back and forth between them.

import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/second': (context) => SecondScreen(),
},
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
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'),
),
),
);
}
}

Navigation output

Upon running our application, our first page renders and shows the following UI.

Our first screen
Our first screen

When we click the "Go to Second Screen" button, the new page opens as shown below.

We can click "Go Back" to return to our first page.

Step by step implementation

Let's dig in to the steps we need to take for the application.

Imports

import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}

We need the material.dart library so that we can utilize Flutter's various widgets and designs. The second step is to define the main. It runs the MyApp widget i.e. the root of the application’s widget tree.

MyApp class

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home: LoginPage(),
routes: {
'/home': (context) => HomePage(),
},
);
}
}

MyApp is a stateless widget we declare using class MyApp extends StatelessWidget. This means that its properties cannot change.

The build method returns a MaterialApp widget, responsible for setting up the basic structure of the app and for defining the routes.

  • debugShowCheckedModeBanner: false: Hides the debug banner of the output.

  • theme: ThemeData.dark(): Sets the application theme to dark mode.

  • home: LoginPage(): The LoginPage widget is set as the first page, so it's displayed when the app starts.

  • routes: {...}: This defines named routes for the application. Since we're redirecting to a home page after logging in, we only define the route '/home', which we link to the HomePage widget.

LoginPage class

class LoginPage extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 100),
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(100),
),
child: FlutterLogo(size: 100),
),
SizedBox(height: 30),
Text(
"Welcome back!",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Text(
"Please log in to continue.",
style: TextStyle(fontSize: 16),
),
),
SizedBox(height: 30),
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: TextField(
controller: _usernameController,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Username",
labelStyle: TextStyle(color: Colors.white),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
borderRadius: BorderRadius.circular(10),
),
),
),
),
SizedBox(height: 20),
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: TextField(
controller: _passwordController,
style: TextStyle(color: Colors.white),
obscureText: true,
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(color: Colors.white),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
borderRadius: BorderRadius.circular(10),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
String username = _usernameController.text;
Navigator.pushNamed(context, '/home', arguments: username);
},
child: Text("Login"),
),
SizedBox(height: 20),
GestureDetector(
onTap: () {
},
child: Text(
"Forgot Password?",
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
),
SizedBox(height: 40),
Text(
"Don't have an account?",
style: TextStyle(fontSize: 16),
),
SizedBox(height: 10),
GestureDetector(
onTap: () {
},
child: Text(
"Sign Up",
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
),
);
}
}

LoginPage class is also a stateless widget showing the UI for the login page. Two TextEditingController objects are created to handle our inputs i.e. username and password.

Its build method returns a Scaffold widget, which provides a structure for the screen i.e. app bar and body. Inside the body, there is a SingleChildScrollView widget to enable scrolling if needed. The login page UI is constructed using various widgets like Container, FlutterLogo, Text, Padding, TextField, ElevatedButton, and GestureDetector. We arrange them in a Column to display them vertically.

The onPressed method of the “Login” button uses the _usernameController to retrieve our entered username and navigates to the HomePage using the named route "/home" we just defined above. Since we wanted to pass the username to the home page, we'll also do that here. Our routing has successfully been implemented through Navigator.pushNamed(context, '/home', arguments: username);.

Note: Server validation for the input, sign up, or forgot password features have not been implemented since the purpose of this code is to demonstrate navigation.

HomePage class

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
String? username = ModalRoute.of(context)?.settings.arguments as String?;
return Scaffold(
appBar: AppBar(title: Text("Home Page")),
body: SingleChildScrollView(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset('assets/cool_image.jpg', height: 200, color: Colors.grey[700]),
SizedBox(height: 20),
Text(
"Welcome to the Home Page, ${username ?? 'Guest'}!",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
Text(
"We hope you have an amazing day here! ",
style: TextStyle(fontSize: 16, color: Colors.grey[500]),
textAlign: TextAlign.justify,
),
],
),
),
);
}
}

Our HomePage class is also a stateless widget representing the home page of our application.

It's build method, as usual, returns Scaffold. Inside the body of this widget, a SingleChildScrollView widget is rendered for scrolling. For constructing our UI, we again use different widgets Image, Text, etc. The Text widget displays a welcome message, including the username that we passed as an argument.

Complete code

Our Flutter code is now complete! It creates a simple login page UI with mainly input fields for the username and password and a login button. After successful login, it navigates to the home page, displaying a welcome message with the username that we gave it or either a default “Guest” in case the username field is left blank.

Don't hesitate to experiment with the code and click "Run" once you're done!

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(),
      home: LoginPage(),
      routes: {
        '/home': (context) => HomePage(),
      },
    );
  }
}

class LoginPage extends StatelessWidget {
  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            SizedBox(height: 100),
            Container(
              padding: EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.grey[800],
                borderRadius: BorderRadius.circular(100),
              ),
              child: FlutterLogo(size: 100),
            ),
            SizedBox(height: 30),
            Text(
              "Welcome back!",
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 10),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                "Please log in to continue.",
                style: TextStyle(fontSize: 16),
              ),
            ),
            SizedBox(height: 30),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: TextField(
                controller: _usernameController,
                style: TextStyle(color: Colors.white),
                decoration: InputDecoration(
                  labelText: "Username",
                  labelStyle: TextStyle(color: Colors.white),
                  enabledBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: Colors.grey),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: Colors.blue),
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
              ),
            ),
            SizedBox(height: 20),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: TextField(
                controller: _passwordController,
                style: TextStyle(color: Colors.white),
                obscureText: true,
                decoration: InputDecoration(
                  labelText: "Password",
                  labelStyle: TextStyle(color: Colors.white),
                  enabledBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: Colors.grey),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: Colors.blue),
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                primary: Colors.blue,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              onPressed: () {
                String username = _usernameController.text;
                Navigator.pushNamed(context, '/home', arguments: username);
              },
              child: Text("Login"),
            ),
            SizedBox(height: 20),
            GestureDetector(
              onTap: () {
              },
              child: Text(
                "Forgot Password?",
                style: TextStyle(
                  color: Colors.blue,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            SizedBox(height: 40),
            Text(
              "Don't have an account?",
              style: TextStyle(fontSize: 16),
            ),
            SizedBox(height: 10),
            GestureDetector(
              onTap: () {
              },
              child: Text(
                "Sign Up",
                style: TextStyle(
                  color: Colors.blue,
                  fontWeight: FontWeight.bold,
                  fontSize: 18,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    String? username = ModalRoute.of(context)?.settings.arguments as String?;

    if (username == null) {
      username = 'Guest';
    }

    return Scaffold(
      appBar: AppBar(title: Text("Home Page")),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Image.asset('assets/intro.png', height: 200),
            SizedBox(height: 20),
            Text(
              "Welcome to the Home Page, $username!",
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 10),
            Text(
              "We hope you have an amazing day here! ",
              style: TextStyle(fontSize: 16, color: Colors.grey[500]),
              textAlign: TextAlign.justify,
            ),
          ],
        ),
      ),
    );
  }
}

When we start our application, this is the page that renders first.

Note: You can add any dummy data in the credentials.

Login page
Login page

Upon adding the credentials and clicking on "Login" we can navigate to the home page. The home page also shows the username we passed to it.

Home page
Home page

We can click the back arrow to go back or alter the code to include proper navigation to the login page.

Conclusion

Efficient navigation is a critical aspect of building multi-page Flutter applications, allowing smooth transitions between screens. By defining routes and using the Navigator class, you can easily move between pages and pass data when needed, enhancing the user experience. Master these core concepts to build more scalable and interactive applications with Flutter.

Note: Explore the Flutter series here!

Navigation in Flutter

Q

If we wanted to pass the order number from the CartPage to Checkout, how would we do it in Flutter?

A)

Navigate.pushNamed(context, '/checkout', arguments: ordernumber);

B)

Navigator.pushName(context, '/checkout', arguments: ordernumber);

C)

Navigator.pushNamed(context, '/checkout', arguments: ordernumber);

Frequently asked questions

Haven’t found what you were looking for? Contact Us


How many types of navigators are there in Flutter?

In Flutter, there are primarily three types of navigators:

  • Basic navigator: The most common type used to manage a stack of routes and move between them. It includes methods like Navigator.push(), Navigator.pop(), and Navigator.pushNamed() for simple page navigation.

  • Navigator with named routes: Uses named routes for more organized and scalable navigation in larger apps. It defines a central routing system, allowing developers to manage multiple screens and navigate using names like /login or /home.

  • Nested navigator: Used for more complex apps that require independent navigation stacks within different sections of the app. This is commonly used for tab-based applications where each tab may have its own navigation flow.


What is the difference between navigation and routing in Flutter?

Navigation: Refers to the action of moving from one screen (or page) to another within the app. It involves actions like pushing a new screen onto the stack or popping a screen to return to the previous one using the Navigator class.

Routing: Refers to the system that defines the paths (or routes) for different screens in the app. Each route corresponds to a specific screen, and routing helps to map these screens within the application. Routes are usually defined in the MaterialApp widget using the routes parameter.

Key difference: Navigation is the process of moving between screens, while routing defines the structure and path of those screens within the app.


Free Resources

Copyright ©2024 Educative, Inc. All rights reserved