Flutter UI Widgets

Nov 9 2021 Dart 2.14, Flutter 2.5, VS Code 1.61

Part 1: Flutter UI Widgets

9. Add Navigation & SliverAppBar

Episode complete

Play next episode

Next
About this episode
See versions

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 8. Style Your App with Themes Next episode: 10. Work with Forms

This video Add Navigation & SliverAppBar was last updated on Nov 9 2021

In previous episodes, we used a FloatingActionButton to add an onPressed interaction. Flutter provides us with some widgets that let’s us build interactivity into any widget. We would be using a GestureDetector widget to do this. This widget detects different types of gestures from a user like a tap, drag, long press and many others.

Open up home_page.dart file. Scroll down to the ListView. And wrap the ArticleCard with this widget and update it like so:

...
final article = snapshot.data![index]; // Dont ignore this!!!
return GestureDetector(
  onTap: () {
    Navigator.push<void>(
      context,
      MaterialPageRoute(
          builder: (context) => ArticlePage(article: article)),
    );
  },
  child: ArticleCard(article: article), // add this Update too!!!
);
...

First, we create an article variable to hold the snapshot data for each index of the listview item. We do this since we’ll be using the snapshot data in multiple places. Then, we wrapped the ArticleCard with a GestureDetector and implemented its onTap callback function. This function would be called whenever this widget is tapped.

Inside this function, we add the code for navigating to a different page. In Flutter, app screens or pages are referred to as routes. Routes are arranged and managed like Stacks. The active route is placed on top of the stack and that would be the current page.

These routes are managed by the Navigator widget. In here, we use its push method to push the ArticlePage to the top of the stack. In other words, it navigates to the ArticlePage.

Note: The ArticlePage is constructed with the use of the MaterialPageRoute widget which serves this widget as a route.

The Navigator widget used here is the provided by the MaterialApp. The ArticlePage widget has already been added to the stater project of this episode. Go ahead and open it. This widget returns a Scaffold with an AppBar and a centered text. We would update this widget soon. Save your work and let’s try it out.

It navigates to the ArticlePage as expected. Notice that the AppBar automatically adds a back button for us. So we can click this to pop this route off the stack. Let navigate to the page once more.

Next, let’s create the contents of the ArticlePage. Open up the article_page.dart file. Update the body of the Scaffold with the following code:

ListView(
  children: <Widget>[
    ArticleMeta(article: article),
    ArticleContent(article: article),
    ArticleTags(),
    Container(
      margin: const EdgeInsets.symmetric(vertical: 16),
      height: 16,
      color: Colors.grey[300],
    )
  ]
),

We use a ListView to wrap the contents of the page to make it scrollable. Its children are a list of custom widgets we’re going to create soon. The last child is a Container which would act as a separator. Let’s create the ArticleMeta widget.

First, comment out other widgets in the ListView then paste the following code:

class ArticleMeta extends StatelessWidget {
  final Article? article;

  const ArticleMeta({
    Key? key,
    required this.article,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: <Widget>[
          Text(
            '${article!.title}',
            style: Theme.of(context).textTheme.headline4!.copyWith(
                  fontSize: 30,
                  fontWeight: FontWeight.w600,
                ),
          ),
          const SizedBox(height: 16),
          ListTile(
            contentPadding: const EdgeInsets.all(0),
            leading: const CircleAvatar(
              child: Icon(Icons.person),
            ),
            title: Text(article!.author),
            subtitle: Text(article!.publishedAt),
            trailing: ElevatedButton(
              onPressed: () {},
              child: const Text(
                'Subscribe',
                style: TextStyle(color: Colors.white),
              ),
              style: ElevatedButton.styleFrom(
                primary: Theme.of(context).primaryColor,
              ),
            ),
          ),
          const SizedBox(height: 16),
        ],
      ),
    );
  }
}

Save your work. This is a StatelessWidget which is used to display information about the article like its title, author information and publish date. It returns a Padding which is used to add some innner spacing. Its child is a Column that has a Text, SizedBox and a ListTile as its children.

The title text uses the textTheme of headline4 and customizes it to a different font size and weight. The fontWeight property is used to adjust the boldness of the text. Note: The textTheme has different styles that corresponds to material design guidelines and headline4 is just one of them.

Next, we use a SizedBox to add some spacing between the Text and the ListTile widget. And you can se the spacing between them. A ListTile is a predefined styled widget provided by Flutter that is used for List related interfaces. For example, you could use this for a chat item in a chat list. Here, we use it to display the author name and other related information.

We set its contentPadding to zero since one of its ancestor already has a padding. We fill up its leading, title, subtitle and trailing properties with widgets and the ListTile widget arranges them accordingly.

And the grey bar below is the Container we added as a separator at the end of all widgets in the ArticlePage. Next, let’s create the ArticleContent widget which houses the main content. Uncomment the ArticleContent line and paste the following code:

class ArticleContent extends StatelessWidget {
  const ArticleContent({
    Key key,
    @required this.article,
  }) : super(key: key);

  final Article article;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
      child: Column(
        children: <Widget>[
          Text(
            '\"${article.description}\"',
            style: Theme.of(context).textTheme.subtitle.copyWith(
                  fontStyle: FontStyle.italic,
                  color: Colors.grey[600],
                  fontSize: 20,
                ),
          ),
          SizedBox(height: 16),
          Text(
            article.content,
            style: TextStyle(fontSize: 18, height: 1.5),
          ),
          SizedBox(height: 16),
        ],
      ),
    );
  }
}

Save your work. You should be familiar with ths code by now but i’ll mention some new additions. For the padding, we previously used the EdgeInsets.all() constructor which adds the padding to all sides of the box.

Now, we use EdgeInsets.fromLTRB() constructor which adds padding from the Left, Top, Right and Bottom. So LTRB corresponds to the argument positions. So we give a padding of 16 on the left, 0 at the top, 16 on both the right and bottom sides of the box. For the content text, we give it a line height of 1.5 which increases the space between each line of text.

Next, let’s add the code for the article tags. Scroll to the top and uncomment the ArticleTags in the ListView. Then paste in the following code at the end of the file:

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Wrap(
        spacing: 8,
        children: <Widget>[
          const Chip(label: Text('Science')),
          const Chip(label: Text('Technology')),
          const Chip(label: Text('Devices')),
        ],
      ),
    );
  }
}

Save your work. This widget displays a list of Chips side by side. The Chips are wrapped with a Wrap widget…no pun intended. The Wrap widget is used when you want to display a list of widget on one axis but need them to move to the next line if they exceed their bounding box. This prevents the overflow warning sign.

Let’s duplicate some of the Chips to show you how it would look like. You can see they wrap to the next line. If you used something like a row, you’ll get the overflow warning sign at the end of the box. So, I’ll undo that…and save our work. Inside the Wrap widget, we added a spacing of 8 between each Chip widget.

For the padding, EdgeInsets.symmetric() is used here to specify the padding on an axis. Here we specified a horizontal padding of 16 which add the padding to the left and right sides.

Finally, we need to show the article’s image. We want to display this at the top of the page and shrink the image as we scroll down the page. The ListView is not built for this type of effect. Even if we were to create this with a ListView, we would need to incorporate other widgets and write many custom codes.

Flutter provides us with the CustomScrollView widget which is used to create custom scroll effects using slivers. A Sliver is just a portion of a scrollable area. In this case, the slivers are areas inside a CustomScrollView.

Update your code to use a CustomScrollView like so:

...
body: CustomScrollView(
  slivers: <Widget>[
    SliverAppBar(
      expandedHeight: 300,
      pinned: true,
      floating: true,
      elevation: 50,
      flexibleSpace: FlexibleSpaceBar(
        background: Image.network(
          '${article.urlToImage}',
          fit: BoxFit.cover,
        ),
      ),
    ),
    SliverList(
      delegate: SliverChildListDelegate(
        [
          ArticleMeta(article: article),
          ArticleContent(article: article),
          ...
        ]
      ),
    )
  ]
),

Save your work. Let’s scroll through the page to see what happens. You can see the appbar expands and shrinks as we scroll the view. Let’s go to another article. Cool, it grows and shrinks as expected.

Here, the CustomScrollView takes in a list of slivers. The first child is a SliverAppBar while the second child is a SliverList. The SliverList is just the sliver version of a normal ListView. We use it here because a CustomScrollView can only accept slivers as its children. And if you notice, we removed the appbar from the scaffold because we’ll be using the SliverAppBar. The SliverAppBar is where is magic happpens.

We set the expandedHeight to 300 which gives the app bar a height of 300. The pinned property is set to true which makes prevents the appbar from scolling out of view. floating is also set to true to make the appbar visible as soon as the user scrolls towards the appbar. You’ll need to play around with these properties to see the different effects they create.

Next, we give it an elevation of 50 to add some shadows below the appbar. Finally, we add a FlexibleSpaceBar and set its background to the article’s image. The flexibleSpace is the portion behind the appbar’s content and it takes up the same height of the appbar. CustomScrollViews are used to add custom scroll effect just like what we just created. (Scroll the view once again)