Flutter - Navigating with a PageView or a BottomNavigationBar

I’m a new Flutter developer, so I’m still getting my sea legs, but I’m already really enjoying the feel of building apps with it. I wanted to share some of what I’ve learned, so let’s pretend we’re tasked with building an app with the following navigation style:

Let’s create a quick homepage that has both of these widgets. By the way, this may look like a lot of code, but it’s mostly created automatically when you run “flutter create <app_name>”. I added the PageView, the BottomNavigationBar, and I added a Page class so that we could have some semi-interesting content in our app:

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Home(),
    );
  }
}
 
class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HomeState();
  }
}
 
class HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title:Text("swiper example")),
      body: PageView(
        children: <Widget>[
          Page(
            title: "Couch",
            assetPath: "assets/couch.webp",
            longDescription: "Couches are objects which humans use to sit upon.",
            color: Colors.lightGreenAccent.withAlpha(30)
          ),
          Page(
            title: "Metroid",
            assetPath: "assets/metroid.png",
            longDescription: "Metroids are creatures which inhabit the world of the Metroid series. The above image is from Super Metroid (1994).",
            color: Colors.lightBlueAccent.withAlpha(30)
          ),
          Page(
            title: "Taft",
            assetPath: "assets/taft.jpg",
            longDescription: "William Howard Taft was the 27th president of the United States. He was also the last president to have cool facial hair. What gives, presidents?",
            color: Colors.redAccent.withAlpha(30)
          ),
        ]
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.weekend), title: Text('couch')),
          BottomNavigationBarItem(icon: Icon(Icons.tag_faces), title: Text('metroid')),
          BottomNavigationBarItem(icon: Icon(Icons.android), title: Text('taft')),
        ]
      ),
    );
  }
}
 
// This widget isn't pertinent to our goals, but it's less boring than looking at plain text
class Page extends StatelessWidget {
  final String title;
  final String assetPath;
  final String longDescription;
  final Color color;
 
  const Page({this.title, this.assetPath, this.longDescription, this.color});
  @override
  Widget build(BuildContext context) {
    return Container(
      color: color,
      child: Padding(padding: EdgeInsets.all(30), child: Column(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        Text(title, style: TextStyle(fontSize: 40, color: Colors.black),),
        Image(height: 250, image: AssetImage(assetPath),),
        Text(longDescription, style: TextStyle(fontSize: 14, color: Colors.black),),
      ]
    )));
  }
}

Here’s a video of all that in action:

Cool, we already have some out-of-the-box swipeable pages, but it’s not connected to our nav bar. And our nav bar doesn’t do anything with the pages. In fact, you can’t even interact with the nav bar.

To fix all this, you just need a few simple things. Firstly, you need a PageController for the PageView. Add it as a field in the State class:

PageController pageController = PageController();

That can be set as the ‘controller’ field in the PageView:

...
body: PageView(
        controller: _pageController,
        children: <Widget>[
...

Then you need to keep track of what page your navbar is focused on. Add a _currentPage to your state:

int _currentPage = 0;

And set that in the BottomNavBar as the ‘currentIndex’:

...
bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentPage,
        items: [
...

Then you can set up an onTap for your BottomNavigationBar. This is pretty straightforward, it animates to the correct page using the _pageController and updates the _currentIndex. Remember to put this in a call to ‘setState’, otherwise, your widget hierarchy won’t know that it needs to redraw:

    onTap: (page) {
          if (page != _currentPage) {
            setState(() {
              _currentPage = page;
              _pageController.animateToPage(page, duration: Duration(milliseconds: 500), curve: Curves.easeInOutCubic);
            });
          }
        },

That already feels pretty glorious. But notice the navbar doesn’t update if a user swipes? To fix that, we just need to listen to our page controller, and update the _currentPage if the user swipes to a new page. A good place to attach this listener is in the initState method:

A little side note here: you may notice that page is a double, meaning it can represent a fraction of a page. PageView and PageController are designed to be flexible enough to handle a wide variety of use cases. Like say you want to implement a “peek” animation to let users know your app is swipeable. You could do that by animating a fraction of a page. That’s why the following code uses “round”, because we want to make sure to only change the page once we’re actually on a different page.

  @override
  void initState() {
    super.initState();
    _pageController.addListener(() {
      if (_pageController.page.round() != _currentPage) {
        setState(() {
          _currentPage = _pageController.page.round();
        });
      }
    });
  }

And that’s it! About 30 lines of code and we’ve got a navigation scheme for our app. This is the finished product:

You can probably tell that this is just scratching the surface for how you could use these elements. PageView, in particular, has all sorts of applications: fancy intro screens, user creation flows, newsfeeds, etc. It can even be used vertically if the need arises. If that doesn’t get your creative juices flowing, then I don’t know what will!

Hopefully, you enjoyed this tutorial, and it made you think about cool projects you could build! Good luck with your Fluttering.

Jon Bedard