Flutter Reference · beginner edition

Flutter, explained for beginners

Flutter is Google's open-source toolkit for building natively compiled apps for mobile, web, and desktop — from a single Dart codebase. This page is a compact reference: the mental model, the syntax you'll actually use, runnable snippets, and a glossary of the jargon people throw around.

Dart 3 Flutter 3.x Material 3 Null safety Hot reload

01 · What is Flutter?

Flutter is a UI toolkit plus a framework plus a rendering engine. You write Dart code that describes what the screen should look like, and Flutter draws every pixel itself instead of handing widgets off to the platform's native UI kit.

Three things in one box

  • Dart — the language you write. Familiar if you know Java, C#, JavaScript, or Swift.
  • Flutter framework — the widget library (material, cupertino, widgets), layout, animation, navigation, gestures.
  • Flutter engine — C++ rendering layer (Impeller/Skia) that compiles your widget tree into GPU draw calls.

What you get

What it is not

02 · How Flutter works (the mental model)

If you remember one diagram, remember this one: you write widgets → Flutter builds a widget tree → elements → render objects → pixels.

LayerWhat it isYou touch it?
Widget An immutable description of part of the UI. Cheap to create, thrown away often. Yes — constantly
Element The long-lived glue that holds a widget's position in the tree and its state. Rarely
RenderObject Does layout (size + position) and painting. Almost never
Layer / GPU Final composited frame sent to the screen. No

Key idea: widgets are immutable blueprints. When something changes, you don't mutate a widget — you rebuild it with new values. Flutter diffs the new tree against the old one and only repaints what actually changed.

The golden rule of layout

Constraints go down. Sizes go up. Parent sets position.

A parent widget passes constraints (min/max width & height) to its child. The child picks a size within those constraints. The parent then decides where to place it. Most "why is my widget overflowing?" bugs are this rule biting you.

03 · Getting set up

  1. Install the Flutter SDK and add it to your PATH.
  2. Install a platform toolchain: Android Studio (Android) and/or Xcode (iOS/macOS).
  3. Install an editor: VS Code (with the Flutter extension) or Android Studio.
  4. Run flutter doctor and fix anything marked with an ✗.
TerminalCopy
# Verify your setup
flutter doctor

# See available devices / emulators
flutter devices

# Create a new project
flutter create my_app
cd my_app

# Run it (with hot reload active)
flutter run

# While running: press r = hot reload, R = hot restart, q = quit

Hot reload vs. hot restart. Hot reload injects changed code and keeps your app state (your counter is still at 7). Hot restart throws away state and starts the app fresh. If hot reload ever behaves oddly, hit hot restart before you debug anything.

04 · Dart in 10 minutes

Dart is the language Flutter is written in. It's statically typed with type inference, garbage collected, and — importantly — has sound null safety.

Variables & typesCopy
// Type inference
var name = 'Flutter';        // String
final version = 3.24;        // set once, at runtime
const pi = 3.14159;          // compile-time constant (must be known at compile time)

// Explicit types
int count = 3;
double ratio = 0.5;
String greeting = 'Hello';
bool isReady = true;

// Collections
List<String> fruits = ['apple', 'banana'];
Map<String, int> scores = {'Ada': 10, 'Linus': 9};
Set<int> ids = {1, 2, 3};

// String interpolation
print('$greeting, $name! Version $version');   // simple
print('Total: ${count + 1}');                  // expression

Null safety — the part that trips people up

Null safetyCopy
String a = 'always non-null';   // cannot be null
String? b;                      // MAY be null — the ? makes it nullable

// b.length;                    // ❌ compile error: b might be null
if (b != null) {
  print(b.length);              // ✅ promoted to non-null inside the check
}

// Null-aware operators
print(b?.length);               // null if b is null, otherwise b.length
print(b ?? 'fallback');         // 'fallback' if b is null
b ??= 'default';                // assign only if currently null

Functions

FunctionsCopy
// Classic
int add(int a, int b) {
  return a + b;
}

// Arrow syntax for single expressions
int add2(int a, int b) => a + b;

// Optional named parameters (the Flutter style)
void greet({required String name, String greeting = 'Hello'}) {
  print('$greeting, $name');
}
greet(name: 'Ada');                       // Hello, Ada
greet(name: 'Ada', greeting: 'Hi');       // Hi, Ada

// Optional positional
void log(String msg, [int? level]) { /* ... */ }

// Functions are values
final doubleFn = (int x) => x * 2;

Classes

ClassesCopy
class Point {
  final double x;
  final double y;

  // Constructor with initializer shorthand
  const Point(this.x, this.y);

  // Named constructor
  Point.origin() : x = 0, y = 0;

  double distanceTo(Point other) {
    final dx = x - other.x;
    final dy = y - other.y;
    return math.sqrt(dx * dx + dy * dy);
  }

  @override
  String toString() => 'Point($x, $y)';
}

final p = Point(1, 2);
final o = const Point.origin();

Collections, loops, and the bits you'll use daily

Collections & control flowCopy
final numbers = [1, 2, 3, 4, 5];

// Functional helpers
final doubled = numbers.map((n) => n * 2).toList();      // [2,4,6,8,10]
final evens   = numbers.where((n) => n.isEven).toList();  // [2,4]
final sum     = numbers.reduce((a, b) => a + b);          // 15
final hasBig  = numbers.any((n) => n > 4);                // true

// Spread & collection-if (great for building widget lists)
final more = [0, ...numbers, if (hasBig) 6];

// for-in
for (final n in numbers) { print(n); }

// switch expression (Dart 3)
final label = switch (numbers.length) {
  0 => 'empty',
  1 => 'one',
  _ => 'many',
};

Classes you'll meet constantly: Future & Stream

Future & StreamCopy
// Future = a value that arrives later (one value)
Future<String> fetchName() async {
  await Future.delayed(const Duration(seconds: 1));
  return 'Ada';
}

// Stream = a sequence of values arriving over time
Stream<int> countdown(int from) async* {
  for (var i = from; i > 0; i--) {
    yield i;
    await Future.delayed(const Duration(seconds: 1));
  }
}

await for (final n in countdown(3)) {
  print(n); // 3, 2, 1
}

05 · Widgets — the building blocks

Everything you see in a Flutter app is a widget: layout, text, images, padding, animation, even the app itself. Widgets nest inside each other to form the widget tree.

StatelessWidget vs. StatefulWidget

StatelessWidgetStatefulWidget
Holds changing data? No — output depends only on its inputs Yes — keeps a State object that can change
Rebuilds when Its parent rebuilds, or inputs change You call setState() (or the parent rebuilds)
Use for Static UI: labels, icons, cards, layouts Interactive UI: counters, forms, toggles, anything animated

Reach for stateless first. Only make a widget stateful when it genuinely owns data that changes over time. Most beginners make everything stateful and end up with a mess.

The two widget shapesCopy
// ---- Stateless ----
class Greeting extends StatelessWidget {
  final String name;
  const Greeting({super.key, required this.name});

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

// ---- Stateful ----
class Toggle extends StatefulWidget {
  const Toggle({super.key});

  @override
  State<Toggle> createState() => _ToggleState();
}

class _ToggleState extends State<Toggle> {
  bool _on = false;

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: _on,
      onChanged: (v) => setState(() => _on = v),
    );
  }
}

The widget catalogue you need on day one

WidgetJob
TextDraws a string, with optional style.
IconDraws a glyph from a font, e.g. Icons.add.
ImageShows an image from assets, network, or memory.
ContainerA box: size, colour, padding, border, radius, shadow.
PaddingAdds space inside around its child.
SizedBoxA fixed-size gap, or forces a child to a size.
Row / ColumnLays children out horizontally / vertically.
StackOverlays children on top of each other.
Expanded / FlexibleTakes up leftover space in a Row/Column.
ListViewA scrollable, lazily-built list.
CardA Material surface with rounded corners and elevation.
ScaffoldThe page skeleton: app bar, body, FAB, drawer, snack bars.
AppBarThe top bar with title and actions.
CenterCentres its child.
GestureDetectorAdds tap/press/drag callbacks to any widget.

06 · Your first app, line by line

lib/main.dartCopy
import 'package:flutter/material.dart';

// 1. Every Dart program starts at main().
void main() => runApp(const MyApp());

// 2. The root widget of the app.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // 3. MaterialApp wires up theming, routing and localization.
    return MaterialApp(
      title: 'Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

// 4. A page. Scaffold gives us the standard page structure.
class HomePage extends StatelessWidget {
  const HomePage({super.key});

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

Why all the const?

A const widget is built once at compile time and reused. When the parent rebuilds, Flutter sees the identical instance and skips the subtree entirely. Sprinkling const is the single easiest performance win in Flutter — and your editor will suggest it.

07 · State & setState

State is any data that can change while the app is running and that affects what's on screen. In a StatefulWidget, you mutate that data inside setState(), which tells Flutter "this widget is dirty, rebuild it."

A counterCopy
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;          // mutate state inside setState
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Text(
          '$_count',
          style: Theme.of(context).textTheme.displayMedium,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Never call setState() from inside build(). It schedules another rebuild, which calls build, which calls setState… infinite loop. Also don't do heavy work inside setState's callback — only the state mutation belongs there.

Lifting state up

When two widgets need the same piece of state, move it to their closest common ancestor and pass it down as a parameter. The child that changes it gets a callback.

Parent owns the state, child reports changesCopy
class Parent extends StatefulWidget {
  const Parent({super.key});
  @override
  State<Parent> createState() => _ParentState();
}

class _ParentState extends State<Parent> {
  String _name = 'World';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Hello, $_name'),
        NameField(
          onNameChanged: (value) => setState(() => _name = value),
        ),
      ],
    );
  }
}

class NameField extends StatelessWidget {
  final ValueChanged<String> onNameChanged;
  const NameField({super.key, required this.onNameChanged});

  @override
  Widget build(BuildContext context) {
    return TextField(onChanged: onNameChanged);
  }
}

When setState isn't enough

For small apps, setState plus lifting is plenty. Once state needs to be shared across many screens, look at:

Beginner advice: don't install a state-management package on day one. Build two or three apps with plain setState first. You'll understand why the packages exist, which makes them much easier to learn.

08 · Layout

Row, Column, and the main/cross axis

Row & ColumnCopy
Column(
  // Column's main axis is vertical, cross axis is horizontal.
  mainAxisAlignment: MainAxisAlignment.center,      // along the column
  crossAxisAlignment: CrossAxisAlignment.start,     // across the column
  children: [
    const Text('Title'),
    const SizedBox(height: 8),
    Row(
      children: [
        const CircleAvatar(child: Icon(Icons.person)),
        const SizedBox(width: 12),
        // Expanded fills the leftover horizontal space.
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: const [
              Text('Ada Lovelace'),
              Text('Mathematician', style: TextStyle(color: Colors.grey)),
            ],
          ),
        ),
      ],
    ),
  ],
)

Padding vs. Margin

Flutter has no margin property. Padding adds space inside a widget's bounds. To get "margin", wrap the widget in a Container with margin:, or put a SizedBox between siblings.

Container: the Swiss army boxCopy
Container(
  width: 200,
  height: 100,
  margin: const EdgeInsets.all(16),          // outside
  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    border: Border.all(color: Colors.black12),
    boxShadow: const [
      BoxShadow(blurRadius: 8, color: Colors.black12, offset: Offset(0, 2)),
    ],
  ),
  child: const Text('Card-like box'),
)

Common constraint errors and their fixes

ErrorCauseFix
"RenderFlex overflowed by N pixels" Row/Column children are too big for the available space Wrap the flexible child in Expanded or Flexible, or make the whole thing scrollable
"Vertical viewport was given unbounded height" A ListView inside a Column with no height limit Wrap the list in Expanded, or use shrinkWrap: true (slower)
"BoxConstraints forces an infinite width" A widget asking for infinite size inside an unbounded parent Give it a size: SizedBox, Expanded, or ConstrainedBox
"Incorrect use of ParentDataWidget" Expanded/Flexible not a direct child of Row/Column/Flex Move it so its immediate parent is a Flex widget

09 · Lists & scrolling

For anything longer than a handful of items, use ListView.builder. It builds items lazily as they scroll into view, so a list of 10,000 rows costs about the same as a list of 10.

ListView.builderCopy
class Todo {
  final String title;
  final String subtitle;
  const Todo(this.title, this.subtitle);
}

class TodoList extends StatelessWidget {
  final List<Todo> todos;
  const TodoList({super.key, required this.todos});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: todos.length,
      // Optional: separate the rows
      separatorBuilder: (_, __) => const Divider(height: 1),
      itemBuilder: (context, index) {
        final todo = todos[index];
        return ListTile(
          leading: const Icon(Icons.check_circle_outline),
          title: Text(todo.title),
          subtitle: Text(todo.subtitle),
          trailing: const Icon(Icons.chevron_right),
          onTap: () => print('Tapped ${todo.title}'),
        );
      },
    );
  }
}

Empty lists matter. A ListView with zero items shows a blank screen that looks broken. Handle it:

Empty / loading / error statesCopy
Widget build(BuildContext context) {
  if (isLoading) {
    return const Center(child: CircularProgressIndicator());
  }
  if (error != null) {
    return Center(child: Text('Something went wrong: $error'));
  }
  if (todos.isEmpty) {
    return const Center(child: Text('Nothing here yet.'));
  }
  return ListView.builder(/* ... */);
}

Making a whole page scroll

SingleChildScrollViewCopy
SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: const [
      Text('Heading'),
      SizedBox(height: 12),
      Text('A long body of text that will scroll...'),
      // ...
    ],
  ),
)

For mixed content — a header plus a list — use CustomScrollView with slivers, or the simpler ListView with a header item at index 0.

10 · Input, buttons & forms

ButtonsCopy
ElevatedButton(
  onPressed: () => print('tapped'),
  child: const Text('Primary'),
)

OutlinedButton.icon(
  onPressed: _submit,
  icon: const Icon(Icons.send),
  label: const Text('Send'),
)

TextButton(
  onPressed: () => Navigator.pop(context),
  child: const Text('Cancel'),
)

// Disable any button by passing null
ElevatedButton(onPressed: null, child: const Text('Disabled'))
A validated formCopy
class SignupForm extends StatefulWidget {
  const SignupForm({super.key});
  @override
  State<SignupForm> createState() => _SignupFormState();
}

class _SignupFormState extends State<SignupForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();   // always dispose controllers
    super.dispose();
  }

  void _submit() {
    // validate() runs every validator and returns true if all pass
    if (_formKey.currentState!.validate()) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Welcome, ${_emailController.text}')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
            decoration: const InputDecoration(
              labelText: 'Email',
              hintText: 'you@example.com',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) return 'Email is required';
              if (!value.contains('@')) return 'Enter a valid email';
              return null;   // null means "valid"
            },
          ),
          const SizedBox(height: 16),
          ElevatedButton(onPressed: _submit, child: const Text('Sign up')),
        ],
      ),
    );
  }
}

Always dispose() your controllers. TextEditingController, AnimationController, ScrollController and FocusNode hold resources. Forget to dispose them and you leak memory.

Handling taps on anything

GestureDetector & InkWellCopy
// No visual feedback — good for custom hit areas
GestureDetector(
  onTap: () => print('tapped'),
  onLongPress: () => print('held'),
  child: const Icon(Icons.favorite),
)

// Material ripple feedback — prefer this for tappable surfaces
InkWell(
  onTap: () => print('tapped'),
  borderRadius: BorderRadius.circular(8),
  child: const Padding(
    padding: EdgeInsets.all(12),
    child: Text('Tap me'),
  ),
)

12 · Async, networking & JSON

async / awaitCopy
Future<String> fetchGreeting() async {
  await Future.delayed(const Duration(seconds: 1));
  return 'Hello from the server';
}

Future<void> main() async {
  print('start');
  final greeting = await fetchGreeting();   // pauses here, doesn't block the UI
  print(greeting);
  print('end');
}
// Output: start → (1s pause) → Hello from the server → end
HTTP + JSON (package:http)Copy
// pubspec.yaml:
//   dependencies:
//     http: ^1.2.0

import 'dart:convert';
import 'package:http/http.dart' as http;

class Todo {
  final int id;
  final String title;
  final bool completed;

  const Todo({required this.id, required this.title, required this.completed});

  // Named constructor: build from a decoded JSON map
  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'] as int,
        title: json['title'] as String,
        completed: json['completed'] as bool? ?? false,
      );
}

Future<List<Todo>> fetchTodos() async {
  final uri = Uri.parse('https://jsonplaceholder.typicode.com/todos');
  final response = await http.get(uri);

  if (response.statusCode != 200) {
    throw Exception('Request failed with status ${response.statusCode}');
  }

  final List<dynamic> data = jsonDecode(response.body) as List<dynamic>;
  return data
      .map((item) => Todo.fromJson(item as Map<String, dynamic>))
      .toList();
}

Showing async data: FutureBuilder

FutureBuilderCopy
class TodoScreen extends StatefulWidget {
  const TodoScreen({super.key});
  @override
  State<TodoScreen> createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  late Future<List<Todo>> _future;

  @override
  void initState() {
    super.initState();
    // Create the future ONCE, not inside build().
    _future = fetchTodos();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<Todo>>(
      future: _future,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }
        final todos = snapshot.data ?? const [];
        if (todos.isEmpty) {
          return const Center(child: Text('No todos.'));
        }
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, i) => ListTile(title: Text(todos[i].title)),
        );
      },
    );
  }
}

Never create the Future inside build(). Every rebuild would fire a fresh network request. Create it in initState (or in a state-management layer) and store it.

Using BuildContext across an await

The mounted guardCopy
Future<void> _save() async {
  await api.save();

  // The user may have navigated away while we waited.
  if (!mounted) return;      // in a State object

  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('Saved!')),
  );
}

13 · Project structure & commands

A typical Flutter projectCopy
my_app/
├── lib/                    # ← all your Dart code lives here
│   ├── main.dart           # entry point
│   ├── models/             # plain data classes
│   ├── screens/            # one file per page
│   ├── widgets/            # reusable UI pieces
│   └── services/           # API clients, storage
├── test/                   # unit & widget tests
├── android/  ios/  web/    # platform-specific shells
├── assets/                 # images, fonts, JSON you ship
├── pubspec.yaml            # dependencies + assets + metadata
└── analysis_options.yaml   # linter rules
pubspec.yamlCopy
name: my_app
description: A sample Flutter app.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '^3.5.0'

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
  provider: ^6.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0

flutter:
  uses-material-design: true
  assets:
    - assets/images/
  # fonts:
  #   - family: MyFont
  #     fonts:
  #       - asset: assets/fonts/MyFont-Regular.ttf
CommandWhat it does
flutter create nameScaffold a new project.
flutter runBuild & launch on the selected device.
flutter run -d chromeRun in a browser.
flutter devicesList connected devices and emulators.
flutter pub getDownload the packages in pubspec.yaml.
flutter pub add httpAdd a dependency and update pubspec.
flutter pub outdatedShow packages with newer versions.
flutter analyzeStatic analysis / lint check.
flutter testRun the test suite.
dart format .Auto-format your code.
flutter cleanDelete build artifacts when things get weird.
flutter build apkProduce a release Android build.

14 · Common beginner mistakes

MistakeWhy it hurtsDo this instead
setState inside build() Infinite rebuild loop Call it from callbacks or lifecycle methods
Creating a Future in build() Refires the request on every rebuild Create it in initState and store it
Forgetting dispose() Memory leaks, "setState after dispose" errors Dispose controllers, focus nodes, listeners
Using context after await The widget may be gone Check if (!mounted) return; first
Huge build() methods Hard to read, everything rebuilds together Extract small widgets into their own classes
Using Column for long lists Builds every child at once; slow and memory heavy Use ListView.builder
Ignoring const Unnecessary rebuilds Let the linter add const for you
Extracting widgets as methods The whole parent still rebuilds Extract as a StatelessWidget class
Forgetting super.key Widget identity/state bugs in lists Always const MyWidget({super.key});
Hardcoding sizes everywhere Breaks on other screen sizes Use Expanded, Flexible, relative units

Widget method vs. widget class

Why classes beat helper methodsCopy
// ❌ Works, but the whole thing rebuilds together and you lose const.
Widget _buildHeader() {
  return const Text('Header');
}

// ✅ Separate widget: Flutter can skip rebuilding it when nothing changed.
class _Header extends StatelessWidget {
  const _Header();
  @override
  Widget build(BuildContext context) => const Text('Header');
}

15 · Cheat sheet

I want to…Use
Show textText('hi')
Add spaceSizedBox(height: 16)
Add paddingPadding(padding: EdgeInsets.all(16), child: …)
Stack verticallyColumn(children: […])
Stack horizontallyRow(children: […])
Fill remaining spaceExpanded(child: …)
Centre somethingCenter(child: …)
Scroll a listListView.builder(…)
Scroll arbitrary contentSingleChildScrollView(child: …)
Make a tappable cardCard(child: InkWell(onTap: …, child: …))
Show a buttonElevatedButton(onPressed: …, child: …)
Show a loading spinnerCircularProgressIndicator()
Show a toast-like messageScaffoldMessenger.of(context).showSnackBar(…)
Open a new screenNavigator.push(context, MaterialPageRoute(…))
Go backNavigator.pop(context)
Get screen sizeMediaQuery.sizeOf(context)
Read the themeTheme.of(context).colorScheme.primary
Rebuild on state changesetState(() { … })
Run code once on mountinitState()
Clean up on unmountdispose()
Show an image from the webImage.network(url)
Add a fixed-size gap in a RowSizedBox(width: 8)

16 · Glossary

The vocabulary you'll meet in docs, Stack Overflow answers and code review.

Widget core
An immutable description of part of the UI. Widgets are cheap, disposable configuration objects — think of them as blueprints, not the building itself. Everything on screen is a widget, including layout, padding and gesture handling.
Element internals
The runtime counterpart to a widget. When Flutter builds a widget, it creates (or reuses) an Element that remembers where that widget sits in the tree and holds its State. You almost never touch elements directly.
RenderObject internals
The object that actually performs layout (computing size and position) and painting. Each widget creates one, but you'll only meet them if you write a custom render widget.
Widget tree
The nested structure formed by your widgets. MyApp contains MaterialApp contains Scaffold contains Column contains Text, and so on.
BuildContext
A handle to where a widget is in the tree. It's how you look up inherited data — Theme.of(context), MediaQuery.of(context), Navigator.of(context). If you call it after an await, check mounted first.
build()
The method that returns the widget tree for a widget. It runs often — keep it cheap and free of side effects: no network calls, no setState, no Future creation.
StatelessWidget
A widget with no mutable state. Its appearance depends only on its constructor arguments. Example: Text, Icon, a static card.
StatefulWidget
A widget that owns mutable state via a companion State object. Changing that state (inside setState) triggers a rebuild. Example: a counter, a form, a checkbox.
State
The long-lived object attached to a StatefulWidget. It survives rebuilds, has initState / dispose lifecycle hooks, and holds your mutable fields.
setState()
The method you call to say "this widget's state changed, please rebuild it." Mutate your fields inside the callback. Never call it during build().
Hot reload
Flutter's superpower: injects edited code into a running app in under a second while preserving state. Triggered with r in the terminal or the ⚡ button in your IDE.
Hot restart
Restarts the app from scratch, discarding all state. Slower than hot reload, but resets everything. Use it when hot reload gives strange results.
const constructor
Marks a widget as compile-time constant so Flutter can create it once and reuse the same instance across rebuilds. Free performance; your linter will nag you to add it.
Key
An identity tag that helps Flutter match widgets between rebuilds. ValueKey for list items that reorder, GlobalKey to reach into a widget from outside (e.g. GlobalKey<FormState>).
Scaffold
The standard page skeleton: app bar, body, floating action button, drawer, bottom bar, snack bars. Most screens are a Scaffold wrapped in a MaterialApp.
Material / Cupertino
The two built-in design languages. Material is Google's (Android-flavoured, default). Cupertino mimics iOS. You can mix them.
Theme
A central object holding colours, typography and component styles. Set it once on MaterialApp and read it anywhere with Theme.of(context).
Sliver
A scrollable piece of UI — the building block of CustomScrollView. SliverAppBar, SliverList. Advanced, but the answer to "sticky header plus scrolling list".
Route
A single screen/page in the navigation stack. Pushed and popped with Navigator.
InheritedWidget
The built-in mechanism for passing data down the tree without threading it through every constructor. Theme and MediaQuery are InheritedWidgets. Most state-management packages are built on this.
Provider / Riverpod / Bloc
Popular third-party state-management approaches. Provider is the friendliest starting point; Riverpod is more modern and testable; Bloc is event-driven and common in larger codebases.
pubspec.yaml
Your project's manifest: name, dependencies, dev dependencies, assets, fonts. Edited directly, or via flutter pub add.
pub.dev
The official package repository for Dart and Flutter. Check a package's "likes", "pub points" and last-updated date before adopting it.
Platform channel / MethodChannel
The bridge that lets Dart call native Kotlin/Swift/Java/C++ code (and vice versa). Needed for platform-specific APIs. You can usually find a pub package that wraps it for you.
Isolate
Dart's unit of concurrency — a separate memory heap and event loop. Use Isolate.run() or compute() for heavy work (parsing huge JSON, image processing) so the UI thread stays smooth.
Event loop
Dart runs your synchronous code on one thread with an event loop. Async functions don't create threads — they yield control and resume later. Blocking that loop blocks the whole UI.
Impeller / Skia
The rendering backends. Skia is the original; Impeller is the newer engine that precompiles shaders to avoid first-run animation jank.
DevTools
A browser-based debugging suite: widget inspector, performance timeline, memory profiler, network view. Launch it from your IDE or flutter run output.
JIT / AOT
Just-In-Time compilation (used in debug mode for fast hot reload) versus Ahead-Of-Time compilation (used for release builds for fast startup and no VM overhead).
Golden test
A widget test that renders a widget and compares the pixels to a stored reference image. Catches unintended visual changes.
Semantics
The accessibility tree Flutter builds alongside the widget tree, used by screen readers. The Semantics widget lets you annotate custom UI.

17 · Idioms & Flutter-isms

Short phrases the community repeats. Each one encodes a real design rule.

"Everything is a widget."
There is no separate styling or layout language. Padding, alignment, gestures, animation, theming — all widgets. If you want to change something, you wrap it in another widget.
"Widgets are immutable blueprints."
You don't change a widget; you replace it with a new one describing the new state. That's why build() returns a fresh tree each time instead of mutating the old one.
"UI = f(state)."
The core mental model. Your screen is a pure function of the current state. Change the state, and Flutter re-runs the function. Stop thinking "show/hide this view" and start thinking "what does the state say?"
"Lift state up."
When two widgets need the same data, move it to their nearest common ancestor and pass it down. Sibling widgets should never own shared state.
"Constraints go down, sizes go up, parents set position."
The layout algorithm in one line. Parent gives constraints → child picks a size within them → parent places the child. Every overflow and unbounded-height error comes from breaking this contract.
"Composition over inheritance."
Don't subclass Container to make a fancy box. Build a new widget that contains the widgets you want. Flutter's whole widget library is built this way.
"Make it const."
The easiest performance advice in Flutter. A const widget can be reused across rebuilds, skipping a whole subtree of work.
"Keep build() pure and cheap."
build() can be called dozens of times per second. No side effects, no I/O, no expensive computation. Precompute in initState or a service.
"Extract a widget, don't extract a method."
Returning widgets from a helper method keeps them in the parent's rebuild scope. A separate StatelessWidget class lets Flutter skip it when its inputs haven't changed.
"If it's stateful, dispose it."
Anything with a lifecycle — controllers, focus nodes, listeners, streams — must be cleaned up in dispose().
"Check mounted after every await."
The widget may have been removed from the tree while you were waiting. Calling setState or using context afterwards throws.
"Push and pop."
Navigation vocabulary from the route stack. You push a screen on top and pop it off. The back button pops.
"It's a package for that."
The community's answer to almost any platform feature. Search pub.dev before writing platform channels by hand.
"Works on my emulator."
The classic reminder that layout that fits one screen will overflow on another. Test on a small phone, a big phone and a tablet — or use the Device Preview package.
"Just hot restart."
When hot reload produces bizarre behaviour (stale state, changed constants not applying), restart before you debug.

18 · Acronyms

AcronymStands forIn Flutter terms
SDKSoftware Development KitThe Flutter SDK you install and add to PATH.
UIUser InterfaceEverything the user sees and touches.
UXUser ExperienceHow the whole interaction feels, not just how it looks.
IDEIntegrated Development EnvironmentVS Code or Android Studio, with the Flutter plugin.
CLICommand-Line InterfaceThe flutter and dart commands.
APIApplication Programming InterfaceThe functions a package exposes; also a remote HTTP service.
JSONJavaScript Object NotationThe usual wire format for API data; decoded with jsonDecode.
HTTPHyperText Transfer ProtocolHow you fetch data; use the http or dio package.
RESTRepresentational State TransferA common style of HTTP API you'll consume.
JITJust-In-Time (compilation)Debug mode — fast rebuilds, slower startup.
AOTAhead-Of-Time (compilation)Release mode — native machine code, fast startup.
VMVirtual MachineThe Dart VM that runs your debug app.
FABFloating Action ButtonThe round button in the corner of a Scaffold.
MD3 / M3Material Design 3The current Material spec; enable with useMaterial3: true.
HIGHuman Interface GuidelinesApple's design spec — what Cupertino widgets follow.
OOPObject-Oriented ProgrammingDart's default paradigm; widgets are classes.
DRYDon't Repeat YourselfExtract repeated UI into reusable widgets.
KISSKeep It Simple, StupidReach for setState before a state-management framework.
YAGNIYou Aren't Gonna Need ItDon't build abstraction layers for screens that don't exist yet.
MVPMinimum Viable ProductThe smallest app worth shipping — often one flutter run away.
CI / CDContinuous Integration / Continuous DeliveryAutomated flutter test and flutter build pipelines.
FPSFrames Per SecondThe smoothness metric; 60 or 120 is the target, 16ms per frame.
DIPDevice-Independent PixelFlutter's logical unit; MediaQuery converts to physical pixels.
a11yAccessibilityScreen readers, contrast, tap-target sizes. Flutter calls this Semantics.
i18n / l10nInternationalization / LocalizationSupporting multiple languages with flutter_localizations.

Where to go next. Build something small and real — a notes app, a weather app, a habit tracker. Read the official Flutter Cookbook whenever you're stuck on a specific UI problem, and search pub.dev before writing anything from scratch.

← Back Flutter ⌂ Encyclopedia Next →