Multiple Scaffolds for each page inside a Flutter App

dora picture dora · Mar 6, 2020 · Viewed 11.6k times · Source

The API documentation: https://api.flutter.dev/flutter/material/Scaffold-class.html says:

The Scaffold was designed to be the single top level container for a MaterialApp and it's typically not necessary to nest scaffolds. For example in a tabbed UI, where the bottomNavigationBar is a TabBar and the body is a TabBarView, you might be tempted to make each tab bar view a scaffold with a differently titled AppBar. It would be better to add a listener to the TabController that updates the AppBar.

Does it mean there needs to be only one single Scaffold under the Material App or one single parent Scaffold for each page. If it's the first, how do we navigate? If the it's later, doesn't it mean the common AppBar and BottomBar get re-rendered on each navigation? What's the best practice.

Answer

drogel picture drogel · Mar 6, 2020

It means that, usually, there should be one Scaffold for each page (more precisely, for each Route/screen), and that you should not nest Scaffolds.

Navigating between screens

For example, take a look at this snippet that you can run in DartPad:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation Basics',
    home: FirstRoute(),
  ));
}

class FirstRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Route'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Open route'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondRoute()),
            );
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

Here, you can see there are two distinct pages/Routes/screens, and each one has its own Scaffold. We are navigating back and forth by using Navigator, and so our pages are being added to the stack, one Scaffold on top of the other. That is fine.

If the it's later, doesn't it mean the common AppBar and BottomBar get re-rendered on each navigation?

Yes, but that is precisely what we want when we make two separate screens, each one with its own Scaffold.

Navigating inside the body of a Scaffold / nested navigations

On the other hand, take a look at this example:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation Basics',
    home: FirstRoute(),
  ));
}

class FirstRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Route'),
      ),
      body: Scaffold(
        body: Center(
          child: RaisedButton(
            child: Text('Open route'),
            onPressed: () {},
          ),
        ),
      ),
    );
  }
}

Here, we are nesting two Scaffolds, and, as you can see, the second app bar is being drawn below the first app bar. That would not be the best approach for tabbed or nested navigations. If you want to navigate inside the body of a Scaffold, and change the app bar depending on the content, using TabControllers, such as DefaultTabController, is preferred. Take a look at this example:

import 'package:flutter/material.dart';

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

class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: [
              Icon(Icons.directions_car),
              Icon(Icons.directions_transit),
              Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}

As you can see, we have used only one Scaffold, since we are dealing with only one screen, really. It just happens that we want to show content pages and navigate inside the body of the Scaffold.

Conclusion

As a general rule of thumb: use only one Scaffold per Route/screen. Use only one Scaffold with widgets such as TabController or IndexedStack to navigate the content inside the body of a single screen.