Flutter Tutorial – Navigation in Flutter between Widgets/Screens

Flutter Tutorial – Navigation in Flutter between Widgets/Screens

Navigation between widgets/screens is very important for all mobile applications. In this post, you are going to learn how to implement navigation in flutter and pass data between Widgets/Screens.

Steps to navigate between Widgets/Screens:

  1. Create two widgets (screens) in your flutter lib folder.
  2. Use Navigator.push(context, Route<T>) method to navigate to Widget/Screen.
  3. Use Navigator.pop(context) method to navigate back to the previous Widget/Screen in the stack.
  4. Implement Named Routing by using initialRoute and routes properties in MaterialApp widget.
  5. Use Navigator.pushNamed(context,routeName) method to navigate between Widgets using named routes.
  6. Share Data between components using arguments property in pushNamed() and pop() method in Navigator

Create Two Widgets / Screens in your Flutter Lib folder:

Create two stateless widgets under lib/screens folder, one is called home_screen.dart and another one is profile_screen.dart.

In home_screen.dart, add a FlatButton widget as follows to navigate to profile screen.

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text('Home Screen Content'),
          FlatButton(
            onPressed: () {},
            child: Text('Navigate to Profile Screen', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor,
          )
        ],),
      ),
    );
  }
}

In profile_screen.dart, create a FlatButton as follows for navigating back to home screen.

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Profile Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text('Profile Screen Content'),
          FlatButton(
            onPressed: () => backToHome(context),
            child: Text('Back to Home!', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor
          )
        ],),
      ),
    );
  }
}

Navigation in Flutter – From Home Screen to Profile Screen

In the home screen button, create a method called navigateToProfile()

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import './profile_screen.dart';

class HomeScreen extends StatelessWidget {
  void navigateToProfile(BuildContext context) {
    Navigator.push(context,
      MaterialPageRoute(builder: (_) => ProfileScreen())
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text('Home Screen Content'),
          FlatButton(
            onPressed: () => navigateToProfile(context),
            child: Text('Navigate to Profile Screen', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor,
          )
        ],),
      ),
    );
  }
}

In order to navigate between widgets/screens, Flutter offers Navigator class which has many methods.

Inside the anonymous function of the onPressed, we are invoking the navigateToProfile(BuildContext context). We pass the context from our build method because we need to use it for the flutter Navigator.push() method.

To navigate to the profile screen, we should use the push method of the Navigator, the push method needs a context as a first argument and Route<T> as the second argument. In our case, we can use built-in MaterialRoute() which flutter offers.

The MaterialRoute(builder: (ctx) => widget()) has a property called builder which accepts a function with a context parameter and returns a Widget. In our case, we need to navigate to Profile Screen. To do that, we are invoking the ProfileScreen() widget inside the builder anonymous method

Navigator.push(
  context,
  MaterialPageRoute(builder: (_) => ProfileScreen())
);

Navigate back to Previous Screens in Flutter – Back to Home Screen

As you see in the preview, that the AppBar itself has a back button for navigating back to the previous screen. But anyway, we may face a situation where we need to create a button inside the body of the scaffold and navigate back to previous screens.

To navigate back, we can use the pop method of the Navigator. The pop() method removes the current widget which was pushed using the push() method, which in turn moves back to the previous screen.

In other words, the navigation in flutter works like a stack. If we push a widget, It will be placed on top of the other screens. If we invoke the pop() method, then the screen at the top of the stack will be removed that results in navigating back to the previous screen.

Let’s create a backToHome method in Profile Screen Widget to handle the Navigator.of(context).pop() as follows

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {

  void backToHome(context) {
    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Profile Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text('Profile Screen Content'),
          FlatButton(
            onPressed: () => backToHome(context),
            child: Text('Back to Home!', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor
          )
        ],),
      ),
    );
  }
}
flutter navigation

Named Routing in Flutter

For a large application, creating a MaterialRoute on the fly for navigation is not an ideal solution. We need to maintain all the routes in one place, to solve that, flutter offers named routing.

Define Routes in MaterialApp

The MaterialApp has a way to define routes for our application. The initialRoute property is used to define the default page or landing page of our application. The routes property is a Map with String as Key and Function which returns Widget as it’s value.

Before defining our routes in the material app, we need to add the route name for our Widgets/Screens. We can hard code the route names in the routes argument but that’s not the ideal way to do, because we need to hard code the route name in all the places where we need to navigate. So, the best practice is to use static property inside each Widget/Screen as follows.

Inside the home_screen.dart, define the routeName property as follows:

  static const routeName = '/';

In your profile_screen.dart, define the routeName property as follows: (you can use any property name you want, I am using routeName)

  static const routeName = '/profile';

Add initialRoute and routes properties in MaterialApp (main.dart), Don’t forget to remove home property when we use initialRoute for our application.

import 'package:flutter/material.dart';
import './screens/home_screen.dart';
import './screens/profile_screen.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        HomeScreen.routeName: (ctx) => HomeScreen(),
        ProfileScreen.routeName: (ctx) => ProfileScreen()
      },
    );
  }
}

The HomeScreen.routeName and ProfileScreen.routeName are the static properties that we have defined in our Widgets. These are mapped to anonymous function which returns our respective widgets.

Navigate using Named Routes in Flutter

To use named routing in our home screen widget, we can replace the push method to pushNamed method. The pushNamed method, the first argument is the context and the second one is the route name. Let’s modify our home screen widget.

  void navigateToProfile(BuildContext context) {
    Navigator.pushNamed(context, ProfileScreen.routeName);
  }

As of now, we are using the stack method of flutter navigation. So to remove the old pages completely from the stack while navigating to the new page, we can replace the pushNamed with pushReplacementNamed. It will clear the current page from the stack and push the new page into the stack.

  void navigateToProfile(BuildContext context) {
    Navigator.pushReplacementNamed(context, ProfileScreen.routeName);
  }

In the preview, you can clearly see that the back button in the app bar is no more provided by flutter. Because the stack has only one item that is the current widget. And also, you can’t use the pop() method if there are no previous screens in the stack. If you press the ‘back to home’ button then you’ll see only a black blank screen.

To move to the previous screen properly, you need to use the pushReplacementNamed method inside the ‘back to home’ button. but I am not going to do that now. As I have to show you how to pass data between routes in the next section.

flutter navigation - named routes

How to pass data between routes – Navigation in Flutter?

In a large application, you may face a situation where you need to pass data between routes, that is from one widget to another widget.

Pass/Share Data using pushNamed or pushReplacementNamed

The pushNamed and pushReplacementNamed have a property called arguments. We can assign any value to the argument and pass it to the navigating widget.

  void navigateToProfile(BuildContext context) {
    Navigator.pushReplacementNamed(context, ProfileScreen.routeName, arguments: 'Some Text Data');
  }

For brevity, I am sharing some text data to the Profile Screen Widget. In order to access the data in the profile screen widget, we need to use ModalRoute.of(context).settings.argument inside the build method.

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {
  static const routeName = '/profile';
  void backToHome(context) {
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
      final textData = ModalRoute.of(context).settings.arguments as string;
    return Scaffold(
      appBar: AppBar(title: Text('Profile Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text(textData),
          FlatButton(
            onPressed: () => backToHome(context),
            child: Text('Back to Home!', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor
          )
        ],),
      ),
    );
  }
}

You can assign the value to a variable, and bind the data in the widget. I have passed the data to the Text Widget above the FlatButton.

Pass/Share Data using Pop method – Navigation in Flutter

The Navigator.pop() method has a second optional argument which is used to pass data to the previous screens when we navigate back. Let’s implement a small example in our profile screen and home screen.

In your profile screen, send a boolean value via pop() method as follows

  void backToHome(context) {
    Navigator.of(context).pop(true);
  }

In your home screen widget, use pushNamed. The pushNamed, push and pushReplacementNamed will return a Future. If you are from a JavaScript background, The Future is a Promise in Dart. You can use then method which accepts a function as a parameter to receive data from the profile screen to the home screen.

Declare a property called showText with a value false in your home screen widget. Add the then method to the pushNamed and update the showText value once it’s resolved.

  bool showText = false;
  void navigateToProfile(BuildContext context) {
    Navigator.pushNamed(context, ProfileScreen.routeName, arguments: 'Some Text Data').then((val) {
      if(val) {
        showText = val;
      }
    });
  }

Based on the showText value, we can show a different Text Widget in our home screen component. Just to test that it’s working. So our final home screen widget code is:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import './profile_screen.dart';

class HomeScreen extends StatelessWidget {
  static const routeName = '/';
  bool showText = false;
  void navigateToProfile(BuildContext context) {
    Navigator.pushNamed(context, ProfileScreen.routeName, arguments: 'Some Text Data').then((val) {
      if(val) {
        showText = val;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          showText ? Text('Data Got From Profile Widget') : Text('Home Screen Content'),
          FlatButton(
            onPressed: () => navigateToProfile(context),
            child: Text('Navigate to Profile Screen', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor,
          )
        ],),
      ),
    );
  }
}

Your profile screen widget code is:

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {
  static const routeName = '/profile';
  void backToHome(context) {
    Navigator.of(context).pop(true);
  }

  @override
  Widget build(BuildContext context) {
      final textData = ModalRoute.of(context).settings.arguments as String;
    return Scaffold(
      appBar: AppBar(title: Text('Profile Screen'),),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          Text(textData),
          FlatButton(
            onPressed: () => backToHome(context),
            child: Text('Back to Home!', style: TextStyle(color: Colors.white)),
            color: Theme.of(context).primaryColor
          )
        ],),
      ),
    );
  }
}

Your main.dart code is:

import 'package:flutter/material.dart';
import './screens/home_screen.dart';
import './screens/profile_screen.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        HomeScreen.routeName: (ctx) => HomeScreen(),
        ProfileScreen.routeName: (ctx) => ProfileScreen()
      },
    );
  }
}

flutter navigation pass data between routes

Conclusion – Navigation in Flutter

I hope, you have learned how to navigate between widgets in flutter and also how to share data back and forth using ModalRoute and pop method in flutter.