Widgets
the tree, composition, and why a margin is a widget too
What it is
In Flutter everything is a widget. A button, text, a margin, alignment, the screen itself. An interface is assembled not out of properties, as in CSS, but out of widgets nested inside one another.
That is the main difference from everything earlier in the book, and the main source of bafflement when reading: the code looks like a deep staircase of brackets.
What it looks like in code
Scaffold(
appBar: AppBar(title: const Text('Weather')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(city.name, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
Text(city.temp == null ? 'no data' : '${city.temp}°'),
const Spacer(),
ElevatedButton(
onPressed: () => onRemove(city.name),
child: const Text('Remove'),
),
],
),
),
)How to read it
Read by nesting, not by lines. The indentation in the code repeats the tree's structure, so the most useful thing while reading is to watch the indentation level.
child is one child, children is a list. Those two words let you
reconstruct the whole structure: where it branches, where it is a chain.
Margins and gaps are widgets too. That is the main difference from CSS:
| What you need | Widget |
|---|---|
| a margin around | Padding |
| a gap between | SizedBox(height: 8) |
| take all the free space | Expanded, Spacer |
| align | Align, Center |
| in a column / in a row | Column, Row |
| stacked on top of each other | Stack |
Which is why the tree is so deep: what would be two lines of properties in CSS is two levels of nesting here.
const in front of a widget. Not a formality: such a widget is created
once and isn't recreated on a redraw. An AI often leaves it out, and the
analyzer complains about that.
Theme.of(context) — take a value from the application's theme: a
colour, a font, a size. The counterpart of CSS variables (article 8.3):
better than writing sizes in as numbers.
Row, Column and overflow
The most frequent visual breakage in Flutter: the content doesn't fit, and a yellow-and-black stripe with an overflow message appears on the screen.
The cause is almost always the same: text longer than the screen was put
into a Row. The cure is wrapping it in Expanded or Flexible — they say
"take the space that's there, and no more".
What to tell the AI
Explain this widget tree: what each part is responsible for.
One line per nesting level, no code.Pull the contents of Column out into a separate CityInfo
widget in lib/widgets/. Pass the parameters through the
constructor. Don't change the behaviour.With a long city name this Row overflows. Fix it with
Expanded, and explain why that rather than a fixed width.What to look for
A ten-level tree in one file. It breaks into widgets.
No const where the widget doesn't depend on data. Needless recreation
on every redraw.
Sizes as numbers instead of the theme. fontSize: 17 instead of
Theme.of(context) — the same magic numbers as in article 8.3.
A Container for one thing. If you only need a margin, that's
Padding; only alignment, Align. Container can do everything at once
and therefore hides the intent.
Logic inside build. Network requests and computations in the build
method: it is called on every redraw, which is to say often.
Fixed sizes for one screen. Phones differ; MediaQuery and Expanded
exist precisely for that.
Cheat sheet
In Flutter everything is a widget, margins and gaps included.
childis one,childrenis a list; the tree follows from them.Read by nesting, not by lines.
constin front of a widget saves a recreation on redraw.Sizes and colours come from the theme, not from numbers.
A yellow-and-black stripe means overflow; the cure is
Expanded.