In Flutter, what do MaterialApp and Scaffold each provide, and why does Scaffold.of(context) sometimes fail to find the Scaffold?
answer
- app level vs screen level
- Navigator, Theme, localizations, messenger
- appBar, body, FAB, drawer, bottom bar
- context above the Scaffold
- Builder or a separate widget
basics
~20 sMaterialApp sets up app-wide services: the Navigator, the Theme, Material localizations and a root ScaffoldMessenger. Scaffold lays out one screen's Material slots. Scaffold.of fails when its context sits above the Scaffold, such as the widget that builds it.
solid answer
~40 s`MaterialApp` is the app-level shell: it creates the `Navigator` and routes, applies `theme`, `darkTheme` and `themeMode` (`ThemeMode.system` by default), installs the Material localizations, and wraps everything in a root `ScaffoldMessenger` for snack bars. `Scaffold` is the per-screen layout: `appBar`, `body`, `floatingActionButton`, `drawer` and `endDrawer`, `bottomNavigationBar`, `bottomSheet` and `persistentFooterButtons`, and it is where snack bars appear. `Scaffold.of(context)` walks up from the given context, so calling it with the context of the widget whose `build` returns the `Scaffold` fails with 'Scaffold.of() called with a context that does not contain a Scaffold.', because that context is above it. I fix it with a `Builder` inside the Scaffold, by extracting the child into its own widget, or with `Scaffold.maybeOf` when absence is acceptable.
code
dart · 42 linesimport 'package:flutter/material.dart';
void main() => runApp(const BikeRentalApp());
class BikeRentalApp extends StatelessWidget {
const BikeRentalApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bike Rental',
theme: ThemeData(colorSchemeSeed: Colors.teal),
home: const RentalHome(),
);
}
}
class RentalHome extends StatelessWidget {
const RentalHome({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Nearby bikes')),
endDrawer: const Drawer(child: Center(child: Text('Filters'))),
body: Center(
// Builder gives a context below the Scaffold; `context` here is above it.
child: Builder(
builder: (innerContext) => OutlinedButton(
onPressed: () => Scaffold.of(innerContext).openEndDrawer(),
child: const Text('Filters'),
),
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {},
icon: const Icon(Icons.qr_code_scanner),
label: const Text('Scan to unlock'),
),
);
}
}go deeper
Recall that MaterialApp is app-wide (navigator, theme, localizations, messenger) and Scaffold is per screen, with slots such as appBar, body, floatingActionButton and bottomNavigationBar.
Explain why Scaffold.of fails with the build method's own context and the fixes: a Builder, a separate widget, a GlobalKey, or maybeOf when absence is normal.
Show judgement about structure: one MaterialApp, one Scaffold per route, nested Scaffolds only with care, and messengers or keys chosen for who needs to reach the Scaffold.
Decide how the app shell is composed, which services live above every route and which per screen, so that features plug into one Scaffold contract.
## MaterialApp: the app-level shell `MaterialApp` is usually the root widget of a Material app. It is not a visible widget; it sets up services that every screen below it relies on: - a **`Navigator`** with the `home`, `routes` or `onGenerateRoute` you give it (`MaterialApp.router` plugs in a `RouterConfig` such as a `GoRouter` instead); - the **theme**: `theme`, `darkTheme` and `themeMode`, which defaults to `ThemeMode.system`; - **localizations** for Material widgets, so dialogs and pickers have their labels; - a root **`ScaffoldMessenger`**, which is why `ScaffoldMessenger.of(context).showSnackBar(...)` works from any screen; - debug aids such as `debugShowCheckedModeBanner`, `true` by default. Most apps have exactly one `MaterialApp`. Nesting a second one creates a second navigator and theme, which is rarely what anyone wants. ## Scaffold: one screen's layout `Scaffold` implements the basic Material screen structure. Each screen, typically each route, has its own `Scaffold`, and it places widgets into named slots: | Slot | Typical content | |---|---| | `appBar` | an `AppBar` with title and actions | | `body` | the screen's main content | | `floatingActionButton` | the primary action, such as 'Scan to unlock' | | `drawer`, `endDrawer` | side panels opened by swipe or button | | `bottomNavigationBar` | a `NavigationBar` | | `bottomSheet` | a persistent sheet anchored to the bottom | | `persistentFooterButtons` | buttons that stay above the bottom bar | The `Scaffold` also displays the snack bars and material banners its nearest `ScaffoldMessenger` sends it. ## The Material 3 AppBar in brief With Material 3, the default since Flutter 3.16, an `AppBar` sits flat at elevation `0.0` and rises to its `scrolledUnderElevation`, `3.0` by default, when content scrolls underneath it. Its toolbar height resolves from the widget, then the `AppBarTheme`, then `kToolbarHeight`, 56 logical pixels. `centerTitle` follows the platform when not set: centred on iOS and macOS when there are fewer than two actions, start-aligned elsewhere. ## Why Scaffold.of fails `Scaffold.of(context)` searches the **ancestors** of `context` for a `ScaffoldState`. A widget's `BuildContext` is its own position in the tree, so the `context` passed to a `build` method that *returns* a `Scaffold` is above that Scaffold, not inside it. The classic failure: 1. `RentalHome.build(context)` returns a `Scaffold`. 2. A button in its `body` calls `Scaffold.of(context).openEndDrawer()` using that same `context`. 3. The search starts above the Scaffold and finds none: 'Scaffold.of() called with a context that does not contain a Scaffold.' Fixes: - wrap the button in a `Builder`, whose `builder` gets a context below the Scaffold; - move the button into its own widget class, which gets its own context below the Scaffold; - keep a `GlobalKey<ScaffoldState>` on the Scaffold and call `key.currentState`; - use `Scaffold.maybeOf(context)` when the caller may legitimately have no Scaffold. Snack bars used to have the same trap, because they were shown through `Scaffold.of`. Since Flutter 2.0 they go through `ScaffoldMessenger.of`, and `MaterialApp` provides a messenger above every route, so any context under the app works. ## A bike-rental home screen The home screen of a bike-rental app is one `Scaffold`: an `AppBar` titled 'Nearby bikes', a map in the `body`, a 'Scan to unlock' `FloatingActionButton.extended`, an `endDrawer` with filters, and a `NavigationBar` in `bottomNavigationBar` for Map, Rides and Account. The `MaterialApp` above it supplies the theme, the navigator that will push the ride-details route, and the messenger that shows 'Bike reserved' snack bars. ## Common structural mistakes - **A second `MaterialApp` inside a screen.** It brings its own `Navigator` and theme, so routes pushed inside it stay inside it and the outer theme stops applying below it. - **A screen with no `Scaffold` or other `Material` ancestor.** Widgets such as `ListTile`, `Checkbox` and `InkWell` run a debug check and fail with 'No Material widget found.', because they need a Material surface to draw ink on. - **Nested Scaffolds without intent.** An inner Scaffold inside a tab is legitimate, but each registered Scaffold under the same messenger shows a snack bar at the same time, so the message can appear twice. - **A `GlobalKey<ScaffoldState>` created inside `build`.** A new key on every build gives the Scaffold a new identity each time and loses its state; create the key once in a `State` field.
- Why can ScaffoldMessenger.of(context) use a context that Scaffold.of(context) cannot?Because the messenger lives higher up. `MaterialApp` inserts a root `ScaffoldMessenger` above every route, so any context inside the app finds it, even the one whose `build` returns the `Scaffold`. `Scaffold.of` needs a context below that particular Scaffold. The messenger then forwards the snack bar to the Scaffold currently registered with it.
- When would you give a Scaffold a GlobalKey instead of using Scaffold.of?When code outside the Scaffold's subtree must open its drawer or query it, for example a parent widget or a controller that holds the key. It avoids hunting for a context below the Scaffold, but a `GlobalKey` must be created once and kept, not rebuilt in `build`, and it ties the caller to one specific Scaffold.
saying these in an interview costs you the question
- Wrap every route in its own MaterialApp to give it a fresh theme.
- Scaffold.of(context) works with the context of the build method that returns the Scaffold.
- MaterialApp draws the app bar, so screens do not need a Scaffold.
- Show snack bars with Scaffold.of(context).showSnackBar.
- A Material 3 AppBar always has a shadow, even with nothing scrolled under it.