skip to content

In Flutter, why does Scaffold.of(context) throw when called from a button in the build method that creates that Scaffold, and how do you fix it?

level: middleimportance: should knowfreq 60%

answer

  1. whose context is it
  2. the builder's context sits above
  3. lookups only walk upward
  4. Builder gives an inner context
  5. or extract a child widget

basics

~20 s

The build method's context belongs to the widget that builds the Scaffold, so it sits above that Scaffold and the upward lookup never meets it. Use a Builder or an extracted child widget to get a context below the Scaffold.

solid answer

~50 s

A widget's context is the parent of everything its `build` returns. When a `build` method creates a `Scaffold` and a button in that same method calls `Scaffold.of(context)`, the lookup starts at the outer widget's element, above the `Scaffold`, and walks toward the root, so it finds nothing and throws 'Scaffold.of() called with a context that does not contain a Scaffold.' The fixes the error message itself lists are: wrap the button in a `Builder` and use the builder's context; split the build into widgets so the inner widget has its own context under the `Scaffold`, which the message calls more efficient; or, less elegantly, give the `Scaffold` a `GlobalKey`. The same trap hits `Navigator.of` with the context of the widget that builds `MaterialApp`. Snackbars no longer have this problem: since Flutter 2.0 they go through `ScaffoldMessenger`, which `MaterialApp` places above your screens.

code

dart · 22 lines
dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('My loans')),
      drawer: const Drawer(child: Text('Renewal history')),
      body: Builder(
        // This context belongs to the Builder, which is below the Scaffold.
        builder: (innerContext) => TextButton(
          onPressed: () => Scaffold.of(innerContext).openDrawer(),
          child: const Text('Show history'),
        ),
      ),
      // Using the outer `context` instead would throw:
      // Scaffold.of() called with a context that does not contain a Scaffold.
    );
  }
}

go deeper

for a junior

Recall that the build method's own context sits above what it builds, and that a Builder gives you a context underneath.

for a middle

Explain the upward-only lookup, why State.context is the same position, the three fixes the error lists, and why ScaffoldMessenger avoids the problem for snackbars.

for a senior

Recognise the silent variants, such as Theme.of returning an outer theme, and steer reviews toward extracted widgets instead of Builder sprawl or GlobalKeys.

for a principal

Set conventions for where screen-level services such as messengers and navigators live so feature code rarely needs to reason about context position at all.

## The failing code A library-loan screen has a drawer with renewal history. The screen's `build` returns `Scaffold(drawer: ..., body: ...)`, and inside the body a button calls `Scaffold.of(context).openDrawer()`. Tapping it throws: 'Scaffold.of() called with a context that does not contain a Scaffold.' The message's description continues: this usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold. ## Why the lookup misses - Every widget has its own `BuildContext`, which is its element. - The `context` parameter of a `build` method is the context of the widget that owns that method. - Everything `build` returns, including the `Scaffold`, becomes a **descendant** of that context. - `Scaffold.of` calls `findAncestorStateOfType<ScaffoldState>()`, which walks parent links **upward** only. The closure in `onPressed` captured the outer `context`, so the walk starts above the `Scaffold` and can never reach it. Using `State.context` in a `StatefulWidget` changes nothing; it is the same element. ## The fixes The framework's error message lists three: 1. **Use a `Builder`**, which the message calls the simplest. Wrap the button in `Builder(builder: (context) => ...)`; the inner `context` belongs to the `Builder`, which sits under the `Scaffold`. 2. **Split the build into widgets**, which the message calls more efficient. Move the button into its own widget class placed inside the `Scaffold`; its context is below the `Scaffold`. 3. **Use a `GlobalKey<ScaffoldState>`** on the `Scaffold` and call `key.currentState`. The message calls it less elegant but more expedient; the key mechanics belong to the keys topic. `Scaffold.maybeOf(context)` does not fix anything: it returns `null` instead of throwing, which only hides the miss. ## The same trap elsewhere | Call | Wrong context | Error or symptom | |---|---|---| | `Scaffold.of(context)` | Widget that builds the `Scaffold` | 'Scaffold.of() called with a context that does not contain a Scaffold.' | | `Navigator.of(context)` | Widget that builds `MaterialApp` | 'Navigator operation requested with a context that does not include a Navigator.' | | `Theme.of(context)` | Widget that wraps its subtree in a new `Theme` | No error, but returns the outer theme instead of the new one | The `Theme` row is the quiet variant: the lookup succeeds but returns the wrong ancestor, which the `BuildContext` documentation uses as its own example. ## Why snackbars stopped failing Before Flutter 2.0, snackbars were shown with `Scaffold.of(context).showSnackBar`, and this trap hit every beginner. The API moved to `ScaffoldMessenger`, and `MaterialApp` inserts a root `ScaffoldMessenger` above all routes. `ScaffoldMessenger.of(context)` from the screen's own build context therefore finds it, and the messenger shows the snackbar on the current `Scaffold`. `ScaffoldState` no longer has a `showSnackBar` method at all. ## Checklist - Ask where the context you are using sits relative to the widget you want. - Prefer extracting a small widget; reach for `Builder` for one-off inline cases. - Do not silence the error with `maybeOf`.

  • Does the same trap apply to Navigator.of?
    Yes. If the widget that builds `MaterialApp` calls `Navigator.of(context)` with its own build context, the lookup starts above the `Navigator` that `MaterialApp` creates and debug builds report 'Navigator operation requested with a context that does not include a Navigator.' Use a context from inside the app's home or a route, where the `Navigator` is an ancestor.
  • Why does the error message call splitting into widgets more efficient than a Builder?
    Both give a context below the `Scaffold`, but an extracted widget class is a real boundary: it can be `const`, skipped when unchanged and rebuilt on its own, and it keeps the screen's `build` shorter. A `Builder` only introduces a context and still reruns with its parent, which makes it fine for small inline cases.

saying these in an interview costs you the question

  • Scaffold.of simply returns null when no Scaffold is found
  • In a StatefulWidget, State.context is below the Scaffold its build returns
  • Lookups also search the widgets returned by the current build
  • Switching to Scaffold.maybeOf is the proper fix for the error
  • Snackbars still have to be shown through Scaffold.of(context)