Flutter Widget Basics

Everything you can see in a Flutter application is a widget. There is no separate “view” layer, no XML layout file, and no way to reach into the screen and change a pixel: you describe what should be on screen as a tree of immutable objects, and Flutter rebuilds the parts of that tree that need to change. That sounds abstract until you see how small the pieces are - a Text is a widget, a Padding around it is a widget, a Row that places them side by side is a widget - and how few of them you need for a complete screen.

1. The Widget Tree

A Flutter application is a tree of widgets rooted at your runApp call.

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      home: Scaffold(
        appBar: AppBar(title: const Text('Demo')),
        body: const Center(child: Text('Hello, Flutter!')),
      ),
    );
  }
}

MaterialApp is the application root: it installs the navigator, the theme, and the directionality that every widget depends on. Inside it, Scaffold supplies the page furniture - an app bar, a body, a floating action button - and the body holds your own widget tree.

The key mental model is this: a widget is a description, not a thing. It is an immutable configuration object that says “put a text here, with this padding, in this alignment”. The element tree that Flutter builds from those descriptions is what actually occupies space and paints pixels.

2. Two Kinds of Widget

StatelessWidget

A stateless widget has no state of its own. It rebuilds whenever its parent rebuilds, and its build method is a pure function of its configuration.

class Greeting extends StatelessWidget {
  const Greeting({super.key, required this.name});

  final String name;

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name');
  }
}

Use stateless widgets for anything that is fully determined by the arguments it is given. Most of your UI will be stateless widgets.

StatefulWidget

A stateful widget owns a State object that survives rebuilds. When the state changes, the framework calls setState, which marks the element dirty and rebuilds that subtree.

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text('Tapped $_count times'),
        ElevatedButton(onPressed: _increment, child: const Text('Increment')),
      ],
    );
  }
}

The rule of thumb: if a value has to survive a rebuild, it belongs in State, not in a local variable of build.

3. Layout Widgets

Flutter offers two families of layout widgets, and choosing the right one is most of layout work.

Flex family

Row and Column are flex widgets: they lay out their children along the main axis. mainAxisAlignment controls distribution along that axis, crossAxisAlignment controls alignment across it, and mainAxisSize decides whether the flex takes all the space it is offered or only what it needs.

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: const [
    Icon(Icons.star),
    Text('12 reviews'),
    Icon(Icons.star),
  ],
)

Expanded and Flexible let a child take a share of the remaining space, and Spacer is an Expanded with no child.

SizedBox, Container, Padding

  • SizedBox(width: 40, height: 20) fixes a size.
  • Padding(padding: const EdgeInsets.all(16), child: ...) adds space around a child.
  • Container is a convenience widget that combines padding, margin, decoration, constraints, and alignment.

Stack, Positioned, and Align

Stack layers children on top of each other; Positioned places a child at an offset inside the stack, and Align positions it by alignment instead.

Stack(
  children: [
    Image.asset('assets/photo.png'),
    Positioned(
      left: 12,
      bottom: 12,
      child: Chip(label: const Text('Sunset')),
    ),
  ],
)

ListView and GridView

ListView builds a scrollable list. ListView.builder creates children lazily, which is what you want for long or unbounded data.

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(
    leading: const Icon(Icons.circle),
    title: Text(items[index].name),
    onTap: () => select(items[index]),
  ),
)

4. Common Widgets

WidgetPurpose
Text / RichTextDisplay text; Text is the usual choice
TextFieldAccept user text input
ImageDisplay an image from assets, network, or memory
IconDisplay a material icon
ElevatedButton / OutlinedButton / TextButtonThe three button emphasis levels
Checkbox / Switch / SliderCapture a boolean or a bounded value
Card / ListTileMaterial surfaces with a title and subtitle
AppBarThe top bar of a Scaffold
CircularProgressIndicatorShow that something is in flight
AlertDialog / BottomSheet / SnackBarShow information and collect decisions

5. Composition: How Widgets Nest

Widgets compose by nesting, and each level adds one constraint to the one below it. A Column imposes a vertical constraint on its children; a Padding insets its child; a Expanded asks for a share of the space its parent has left.

Center(
  child: Container(
    padding: const EdgeInsets.all(24),
    decoration: BoxDecoration(
      color: Theme.of(context).colorScheme.surfaceContainerHighest,
      borderRadius: BorderRadius.circular(16),
    ),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Icon(Icons.check_circle_outline, size: 48),
        const SizedBox(height: 12),
        Text('All done', style: Theme.of(context).textTheme.titleLarge),
      ],
    ),
  ),
)

Theme.of(context) is the idiomatic way to read the current theme: colours, text styles, and spacing all come from the theme rather than from hard-coded values, so a widget built this way looks right in both light and dark mode.

6. Rebuilds and const

Because widgets are immutable descriptions, Flutter can skip rebuilding a subtree when the new widget is identical to the old one. A const constructor tells the compiler that a widget is canonical, so two const Text('x') widgets are the same object and the subtree is not rebuilt.

const Center(child: Text('Static'));

Make constructors const wherever the arguments allow it. It is not a micro-optimisation; it is the mechanism that keeps large widget trees cheap.

Practice

FlutterWidget TreeStatelessWidgetLayoutconst Constructor

Approach: The question asks you to predict what the framework does with a specific widget tree, so reason from the rebuild rules rather than from a general knowledge of Flutter: a widget is a description, only a change of description triggers a rebuild, and const canonicalisation is what makes “no change” detectable.

Steps:

  1. const Center(child: Text('Static')) is canonical, so the Center widget instance is identical on every build and the framework can skip rebuilding the subtree entirely.
  2. Center passes loose constraints to its child and sizes itself to the child, so the Text is laid out at the centre of whatever space the Center receives.
  3. Center positions its child by alignment (0.5, 0.5) of the available space, so the text ends up in the middle, not at the top-left.
  4. If the Text had not been const, a new widget instance would be created on each build, the subtree would be rebuilt, and the result would look identical but cost more.

Answer: The text is rendered in the centre of the available space, the subtree is not rebuilt because the const widget is canonical, and the Center takes whatever space its parent offers while positioning the child at the alignment centre.

Where to Go Next

Now that you can describe a screen as a tree of widgets, the next question is where the data lives: how you hold changing values, and how a change in one part of the tree is allowed to update another part without the two being entangled.

Found an issue with this content?

Tell us what could be improved.