Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Flutter

Flutter Getting Started: Tutorial 3 Navigation

5.00/5 (1 vote)
12 Jul 2018CPOL8 min read 17.4K   165  
Flutter Navigation, Let's move among pages

Introduction

In this article, I would discuss Flutter Navigation, Navigation or movement among pages is important business requirement of any application, for example almost every application requires a login page or option page, where various options could be set based on user input.

In simpler terms, it means leaving one page and loading a new page. Based on code, page may persist in memory or be simply deleted from stack. So at most, care should be taken by Developer/Architect while developing an application, what should be kept in memory and what shouldn't.

Background

If you happen to read my article, Flutter Getting Started: Tutorial 2 - StateFulWidget, we will build on the same application. Let me summarize what I have done in that article - I have provided a TextField which is similar to TextBox in C#, and on press of Save button, it gets added to list which also presents the same page.

Now, we destroy that application and create two pages out of it, one which will display the list of cities added and the other page provides options for adding the cities. So this would be the final result once the application is completed.

Add City new UI design

Image 1

Here, as mentioned above, form portion going into AddDataPage file and Listview which kept list of data, added by user would become part of home page. I marked two new additions with yellow squares, one for invoking AddDataPage and another for going back to HomePage.

Aim of the Tutorial

As mentioned earlier in this article, I will be building an application based on my previous article. I will be creating two pages application and in the end, I will be adding additional pages to showcase removing of multiple pages.

  • There would be two pages:
    • Page #1: Homepage would contain ListView, where all the user input city would be displayed
    • Page #2: AddDataPage would contain TextField from where you can add data to list (it contains TextField, Cancel and Add RaisedButton )
  • Task #1: Basic Navigation, when we navigate between pages using known page object
  • Task #2: Sending data using Navigation object
  • Task #3: Using named route for navigation instead of page object
  • Bonus Task#4: Additional page would be added to show navigation stack, and how pages persist between navigation

Overall, application would contain two pages, one would show all the text entered by user and the other page would provide form to enter data.

Using the Code

  1. Let's start by creating a new flutter project in Android Studio, Give it name Flutter3_navigation. In case you don't know how to create Flutter project, please read here.
  2. Create a package in lib directory by name pages and add two DART files:
    1. adddatapage.dart: Add AddDataPage class which extend Statelesswidget
    2. homepage.dart: Add HomePage class which extend StateFulWidget (if you don't know about StateFulWidget, read here), also add _HomePageState which extends state<HomePage>
  3. Delete all code from main.dart, the entry point of Mobile Application, and replace with this code:
    C++
    import 'package:flutter/material.dart';
    import 'package:flutter3_navigation/pages/homepage.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(        
            primarySwatch: Colors.blue
          ),
          home: new HomePage(title: 'Flutter 3 : Navigation'),
        );
      }
    }

    Explanation of Code

    • Here, MyApp is our entry class for mobile application, here we return MaterialApp object. This will provide skeleton for our app.
    • I set home object to HomePage object, which means it would be loaded when application starts running.
  4. Put the following code in HomePage.dart, the following control has been added:
    C++
    import 'package:flutter/material.dart';
    import 'package:flutter3_navigation/pages/adddatapage.dart';
    
    class HomePage extends StatefulWidget {
      final String title;
      HomePage({Key key, this.title}) : super(key: key);
    
      @override
      _HomePageState createState() => new _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text(
              widget.title,
              style: new TextStyle(fontSize: 16.0),
            ),
            actions: <Widget>[
              new IconButton(
                  icon: new Icon(Icons.add),
                  onPressed: () {
                    print("Add button clicked");                
                  })
            ],
          ),
        );
      }}
    1. Here, we create page Scaffold and create AppBar inside it.
    2. Here is a new addition in App Bar, I have added IconButton with display of Icons.add. This is a placeholder for navigating to AddDataPage, currently we are not doing anything:
      Image 2
  5. Now, before I show you code for AddDataPage.dart, let me give you a small overview of Navigator Class. We generally use the following function (definition taken from Flutter website):
    1. Navigator.push - Push the given route onto the navigator that most tightly encloses the given context
    2. Navigator.pop - Pop the top-most route off the navigator that most tightly encloses the given context
    3. Navigator.pushNamed - Push a named route onto the navigator that most tightly encloses the given context. We are going to use it in Task#3.
    4. Navigator.pushNamedAndRemoveUntil - Push the route with the given name onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true. We are going to use in Task#4.
  6. Now, add function void _onAddCityButtonPressed(BuildContext context) in HomePage.dart file and call that from Add IconButton:
    C++
    void _onAddCityButtonPressed(BuildContext context)  {
      Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => AddDataPage())
      ) }

    Explanation of Code

    • As mentioned in step 5, I am using Navigator.Push to push new page on stack, here we need to use current BuildContext and Push AddDataPage, when person clicks on + button in AppBar.
  7. Now it's the turn of AddDataPage.dart. Add the following code:
    C++
    import 'package:flutter/material.dart';
    
    class AddDataPage extends StatelessWidget {
      final TextEditingController _CityNameController = new TextEditingController();
    
      @override
      Widget build(BuildContext context) {
    
        return new Scaffold(
            appBar: new AppBar(
              title: new Text(
                "Add City",
                style: new TextStyle(fontSize: 16.0),
              ),
            ),
            body: new SafeArea(
              child: new Container(
                child: new Column(children: <Widget>[
                  new TextField(
                    decoration: new InputDecoration(
                      labelText: "City Name",
                    ),
                    controller: _CityNameController,
                  ),
                  new ButtonBar(
                    alignment: MainAxisAlignment.end,
                    children: <Widget>[
                      new RaisedButton(
                        onPressed: () {
                          Navigator.pop(context);
                        },
                        child: new Text("Cancel"),
                      ),
                      new RaisedButton(
                        onPressed: () {
                          Navigator.pop(context,_CityNameController.text);
                        },
                        child: new Text("Add"),
                      )
                    ],
                  )
                ]),
              ),
            ));
      }
    }

    Explanation of Code

    • Here in this page, I have added a TextField, a ButtonBar with two button Cancel (which will pop the current page) and Add (which will return whatever data is entered in TextField).
    • For returning data when unloading page, you can use the second parameter with whatever data you want to return. In our case, CityName.

      Image 3

    Here, we come to the end of Task#1.
  8. Now we are sending the data, but we haven't written code to catch whatever is coming from the other page, the answer lies in Future class, its type of object which keeps track of object coming in future and lets you catch its linked function. I would be writing a full-fledged article using async function and Future in Dart language. It is similar to Observable/ Promises in Angular. Here is how we going to catch CityName sent by AddDataPage to HomePage. Here is a modified function that looks like:
    C++
    void _onAddCityButtonPressed(BuildContext context)  {
      Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => AddDataPage())
      )
      .then((Object result){
        print("result from addpage ${result.toString()}");
      });
    }

    Explanation of Code

    • then function is part of Future class, this function will be anchor to page you navigate to, and bring you data whatever sent from there.
    • There are various ways to achieve this (gathering of data), I am showing only one way here, as it's not fruitful to tell them before letting you know how async works in DART.
  9. Now I modified code to include ListView in the HomePage and adding data received from AddDataPage, all explanation of the below code is available in my previous article here.
    C++
    final List<Text> _lstCities = new List<Text>();
    
      Widget _getListViewFromBuilder() {
        return ListView.builder(
          itemCount: _lstCities.length,
          shrinkWrap: true,
          padding: const EdgeInsets.all(16.0),
          itemBuilder: getListItems,
        );
      }
    
    // Call back function, will called for each item in the
      Widget getListItems(BuildContext context, int index) {
        return _lstCities[index];
      }
    
      // this function would called when add city button pressed
    
      void _onAddCityButtonPressed(BuildContext context) {
        Navigator
            .push(context, MaterialPageRoute(builder: (context) => AddDataPage()))
            .then((Object result) {
          if (result == null) return;
          setState(() {
            _lstCities.add(new Text(
              "${_lstCities.length + 1} ${result.toString()}",
              textAlign: TextAlign.justify,
              style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 24.0),
            ));
          });
        });
      }
    Image 4

    Here, we come to end of Task#2.

  10. Now the problem with the above code, the routing is fixed, i.e., since we are navigating through providing new object for page we want to load, this creates a problem if we want to have dynamic routes and avoid references of pages in a different project (you need to import class file, before providing page object).
  11. Now you have to modify the Main.dart with the following changes, home property needs to be commented out, as we are relying completely on routing now:
    C++
    //home: new HomePage(title: 'Flutter 3 : Navigation'),
    routes: {
      '/' : (BuildContext build)=> new HomePage(title: 'Flutter 3 : Navigation'),
      '/adddata':(BuildContext build)=> new AddDataPage(),
    },
      initialRoute: "/"

    Explanation of Code

    • Here, I commented out home property, as we are no longer using it.
    • Add route property, which is map of String as key and WidgetBuilder as value, as going forward, we need string key to identify route we need to create.
    • Now instead of Navigator.push in HomePage, use Navigator.pushNamed:
      C++
      /*
       Task # 1 and 2
      
        Navigator
        .push(context, MaterialPageRoute(builder: (context) => AddDataPage()))
           */
      /*
       Task 3 and 4
       */
           Navigator.pushNamed(context,'/adddata' )
           .then((Object result) {
    • In the above, since we want to navigate to AddDataPage, we use its String key to navigate.
    Here, we come to the end of Task#3.
  12. Now for the bonus part, here we would be adding a new page by name EndPage and I would include bottom bar in both AddDataPage and EndPage.
    1. AddDataPage bottom bar would contain forward navigation to end, which will use Navigator.pushNamed to reach EndPage
    2. EndPage bottom bar would have one home button, which navigates to home page using pushNamedAndRemoveUntil and backward button to move HomePage
  13. Now modified AddDataPage looks like, inside page container add bottomNavigationBar and new function _getBottomNavigationBarWidget():
    C++
    bottomNavigationBar: _getbottomNavigationBarWidget(context),

    New function needs to be added at the bottom of class, don't forget to route definition for EndPage in main.dart, as mentioned in Step 11.

    C++
    Widget _getbottomNavigationBarWidget(BuildContext context) {
      return Container(
        color: Colors.black87,
    
        child: new ButtonBar(
          mainAxisSize: MainAxisSize.max,
          alignment: MainAxisAlignment.end,
          children: <Widget>[
            new IconButton(icon: new Icon
            (Icons.arrow_forward,size: 24.0,color: Colors.white,), onPressed: (){
               Navigator.pushNamed(context, "/endpage");
            })
          ],
        ),
      );
    }

    Here, we added an IconButton in bottombar, on pressing the button, we navigate to endpage:

    Image 5

  14. Add a new dart file in pages folder by endpage.dart, and add EndPage class to it, since we are not managing any state inside it, we can safely derived it from StatelessWidget, code would look like this:
    C++
    import 'package:flutter/material.dart';
    
    class EndPage extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
    
        return new Scaffold(
          appBar: new AppBar(
            title: new Text(
              "End Page",
              style: new TextStyle(fontSize: 16.0),
            ),
          ),
          body: new Center(
            child: new Text("Welcome to End Page",
            style: new TextStyle(fontSize: 24.0,fontWeight: FontWeight.bold),),
          ),
          bottomNavigationBar: _getbottomNavigationBarWidget(context),
        );
      }
    
      Widget _getbottomNavigationBarWidget(BuildContext context) {
        return Container(
          color: Colors.black87,
    
          child: new ButtonBar(
            mainAxisSize: MainAxisSize.max,
            alignment: MainAxisAlignment.end,
            children: <Widget>[
              new IconButton(icon: new Icon
              (Icons.arrow_back,size: 24.0,color: Colors.white,), onPressed: (){
                Navigator.pop(context);
              }),
              new IconButton(icon: new Icon
              (Icons.home,size: 24.0,color: Colors.white,), onPressed: (){
    
                Navigator.pushNamedAndRemoveUntil(context, "/", 
                          (Route<dynamic> route){ return false;});
              })
            ],
          ),
        );
      }
    }

    Here, we have two buttons in BottomNavigationBar, on pressing backward button, we pop current page and move to AddDataPage. On pressing Home button, we remove all pages till we show HomePage.

    Image 6

    Here, we come to the end of Task#4.

  15. Here is a GIF file of final output.

    Image 7

Though there are a lot of things left, like Page Animation during transition, etc. Hopefully, I have covered all basic maneuvering in this article. Please don't hesitate to ask any question or comment.

Points of Interest

Please go through these articles. It might give you a headway, where the wind is actually flowing:

  1. Flutter — 5 reasons why you may love it
  2. What’s Revolutionary about Flutter
  3. Why Flutter Uses Dart
  4. Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter3_navigation

Flutter Tutorial

  1. Flutter Getting Started: Tutorial 1 Basics
  2. Flutter Getting Started: Tutorial 2 - StateFulWidget

Dart Tutorial

  1. DART2 Prima Plus - Tutorial 1
  2. DART2 Prima Plus - Tutorial 2 - LIST

History

  • 12th July, 2018: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)