Learn how to build layouts with common widgets like Scaffold, AppBar, Column, and Row.
Introduction
Given that Flutter is a UI toolkit, you'll spend a lot of time creating layouts with Flutter widgets.
In this section, you'll learn how to build layouts with
some of the most common layout widgets.
This includes high-level widgets like
Scaffold and AppBar, which lay out the structure of a screen,
as well as lower-level widgets like Column or Row that
lay out widgets vertically or horizontally.
Scaffold and AppBar
Mobile applications often have a bar at the top called an "app bar" that can display a title, navigation controls, and/or actions.
The simplest way to add an app bar to your app is by using two widgets:
Scaffold and AppBar.
Scaffold is a convenience widget that provides a Material-style page layout,
making it simple to add an app bar, drawer, navigation bar,
and more to a page of your app.
AppBar is, of course, the app bar.
Add an AppBar to the Scaffold widget
within your MainApp widget's build method.
The following code uses an additional layout widget: Align.
This positions the title to the left, which would be centered by default.
The Text widget contains the title itself.
Passing an enum or static property directly (like Alignment.centerLeft)
can also be shortened using Dart's dot shorthands syntax,
which you can read more about on both the official Dart documentation
and the Flutter shorthands overview.
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Align(
alignment: Alignment.centerLeft,
child: Text('Birdle'),
),
),
body: const Center(
child: Tile('A', HitType.hit),
),
),
);
}
}An updated widget tree
Considering your app's widget tree gets more important as your app grows. At this point, there's a "branch" in the widget tree for the first time, and it now looks like the following figure:
Create a widget for the game page layout
Add the following code for a new widget,
called GamePage, to your main.dart file.
This widget will eventually display the UI elements needed for the game itself.
class GamePage extends StatelessWidget {
GamePage({super.key});
// This object is part of the game.dart file.
// It manages wordle logic, and is outside the scope of this tutorial.
final Game _game = Game();
@override
Widget build(BuildContext context) {
// TODO: Replace with screen contents
return Container();
}
}Then update your MainApp widget to create and
display a GamePage widget instead of the Tile widget.
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Align(
alignment: Alignment.centerLeft,
child: Text('Birdle'),
),
),
body: Center(child: GamePage()),
),
);
}
}Because GamePage initializes a non-constant field
(final Game _game = Game();),
its constructor cannot be const.
If you previously had const Center(...) or const MaterialApp(...),
Dart reports an error:
"The constructor being called isn't a const constructor."
To resolve this, remove the const keyword from Center (or MaterialApp)
(or the enclosing parent widget)
and only use const on widgets with constant arguments,
like const Align(...).
The GamePage layout contains the grid of tiles that display a user's guesses.
There are a number of ways you can build this layout.
The simplest is with the Column and Row widgets.
Each row contains five tiles that represent the five letters in a guess,
with five rows total.
So you'll need a single Column with five Row widgets as children,
where each row contains five children.
To get started, replace the Container in GamePage.build with a
Padding widget with a Column widget as its child:
class GamePage extends StatelessWidget {
GamePage({super.key});
// This manages game logic, and is out of scope for this lesson.
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 5.0,
children: [
// Add children next.
],
),
);
}
}The spacing property puts five pixels between each element on the main axis.
Within Column.children, for each element in the _game.guesses list,
add a Row widget as a child.
elements, one for each potential guess. The list will always contain exactly five elements, and therefore will always render five rows.
class GamePage extends StatelessWidget { GamePage({super.key}); // This manages game logic, and is out of scope for this lesson. final Game _game = Game();
@override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: [ for (final guess in _game.guesses) Row( spacing: 5.0, children: [ // We'll add the tiles here later. ], ), ], ), ); } }
The `for` loop in the `children` list is called a [collection for element][],
a Dart syntax that allows you to iteratively add items to a collection
when it is built at runtime.
This syntactic sugar makes it easier for you to work
with collections of widgets,
providing a declarative alternative to the following:
```dart
[..._game.guesses.map((guess) => Row(/* ... */))],In this case, it adds five Row widgets to the column,
one for each guess on the Game object.
An updated widget tree
The widget tree for this app has expanded significantly in this lesson. Now, it looks more like the following (abridged) figure:
Add a Tile to each row for each letter allowed in the guess.
Each element in guess is a record with the type
({String char, HitType type}).
Use a nested loop to iterate over the letters in each guess.
Solution:
class GamePage extends StatelessWidget {
GamePage({super.key});
// This manages game logic, and is out of scope for this lesson.
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 5.0,
children: [
for (final guess in _game.guesses)
Row(
spacing: 5.0,
children: [
for (final letter in guess) Tile(letter.char, letter.type),
],
),
],
),
);
}
}