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.
It means that, usually, there should be one Scaffold
for each page (more precisely, for each Route
/screen), and that you should not nest Scaffold
s.
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/Route
s/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
.
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 Scaffold
s, 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
.
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.