Chuyển đến nội dung chính

BuildContext is an element: reading the error messages that mention it

BuildContext is the parameter everyone types a thousand times without ever asking what it is. It shows up in every build method, it is required by Theme.of, Navigator.of, showDialog, MediaQuery.sizeOf — and then one day it produces an error that makes no sense, like Scaffold.of() failing inside a widget that is very obviously wrapped in a Scaffold.

The declaration answers the whole thing. In the framework source, abstract class Element extends DiagnosticableTree implements BuildContext. A BuildContext is an Element, exposed through a narrow interface so you cannot mutate the tree with it. When a build method receives a context, it is being handed its own element — its exact position in the live tree.

Everything confusing about BuildContext becomes obvious once you read it as “my node in the tree” rather than “the app.”

Lookups walk upward from your node

Theme.of(context), MediaQuery.of(context), Navigator.of(context) and friends all do the same thing: start at that element and walk up the ancestor chain until they find what they are looking for. They never look down, and they never look sideways.

Which explains the classic failure:

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Throws: no Scaffold above THIS context.
            Scaffold.of(context).openDrawer();
          },
          child: const Text('Open'),
        ),
      ),
    );
  }
}

The context in that closure is MyPage’s context. The Scaffold is created below it, as part of what MyPage returns. Walking up from MyPage finds whatever wraps the page — never the Scaffold inside it.

The framework’s own error message says this clearly if you read it as written: it complains that the context used was one “that does not include the Scaffold.” The fix is to look up from a node that is genuinely underneath:

Builder(
  builder: (innerContext) => ElevatedButton(
    onPressed: () => Scaffold.of(innerContext).openDrawer(),
    child: const Text('Open'),
  ),
)

Builder is a widget whose entire purpose is to create one extra element so you get a context one level deeper. It has no visual effect at all. It exists solely to move you down the tree.

Two alternatives worth knowing. Splitting the subtree into its own widget gives the same new context and is usually cleaner. And for Scaffold specifically, ScaffoldMessenger.of(context) — the modern way to show a SnackBar — is deliberately looked up from above the Scaffold, so page-level contexts work fine.

.of() versus .maybeOf() versus .sizeOf()

The naming convention across the framework is consistent, and each variant means something different at runtime:

CallReturnsWhen it is missingSubscribes to changes
X.of(context)The valueThrows with a long diagnosticYes
X.maybeOf(context)X?Returns nullYes
MediaQuery.sizeOf(context)Just the sizeThrowsOnly to size changes

That last row is a real performance tool. MediaQuery.of(context) makes your widget rebuild when anything in the MediaQueryData changes — text scale, padding, view insets, brightness, and notably the keyboard sliding in and out. If all you wanted was the width, MediaQuery.sizeOf(context) rebuilds only when the size changes. The same pattern exists for textScalerOf, paddingOf, viewInsetsOf, platformBrightnessOf and others, and swapping to the specific one is a free win in any widget that sits above a keyboard.

What “depending on” an inherited widget means

The subscription in that table is not a metaphor. Theme.of(context) is implemented roughly as:

static ThemeData of(BuildContext context) {
  final inherited = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
  // ...
}

dependOnInheritedWidgetOfExactType does two things: it finds the nearest ancestor of that type, and it registers your element as a dependent. When that inherited widget is later rebuilt with data for which updateShouldNotify returns true, every registered dependent is marked dirty. That is the whole of Flutter’s built-in reactive propagation — InheritedWidget plus a dependency registry keyed by element.

Which is why this throws:

@override
void initState() {
  super.initState();
  final theme = Theme.of(context);   // error: called before initState completed
}

Registering a dependency requires the element to be fully mounted and able to be marked dirty; during initState it is not yet. The right places are didChangeDependencies (called immediately after initState, and again whenever a dependency changes) or build.

There is a non-subscribing sibling for the rare cases where you want a one-shot read that will not cause rebuilds: getInheritedWidgetOfExactType. Use it deliberately, and know that you will not be notified when the value changes.

Context after an await is the dangerous one

This is the rule that survives contact with production code, and the lint that enforces it — use_build_context_synchronously — is on by default in flutter_lints.

Future<void> _save() async {
  await repository.save(draft);
  Navigator.of(context).pop();          // unsafe
}

Between the await and the next line, anything can happen: the user pressed back, a parent rebuilt this subtree away, the route was popped by a deep link. If the element was unmounted, its ancestor chain is gone and looking anything up from it is undefined at best.

The fix is to check, and the check has to come after the await, in the same synchronous block as the use:

Future<void> _save() async {
  await repository.save(draft);
  if (!context.mounted) return;
  Navigator.of(context).pop();
}

context.mounted was added precisely for this and works for a plain BuildContext, including inside a StatelessWidget’s callbacks. Inside a State, mounted on the state object means the same thing.

The better structural answer, where you can take it, is to capture what you need before the await:

Future<void> _save() async {
  final navigator = Navigator.of(context);
  final messenger = ScaffoldMessenger.of(context);

  await repository.save(draft);

  navigator.pop();
  messenger.showSnackBar(const SnackBar(content: Text('Saved')));
}

NavigatorState and ScaffoldMessengerState outlive the widget that looked them up, so this is safe even if the calling widget is gone — and it removes the whole class of “is my context still valid” reasoning from the code.

Dialogs, and the context you must not reuse

showDialog hands the builder a different context, belonging to the dialog’s own route. Mixing the two up produces the most common dialog bug in Flutter:

showDialog(
  context: context,
  builder: (dialogContext) => AlertDialog(
    actions: [
      TextButton(
        // Wrong: pops the page, not the dialog — or pops both.
        onPressed: () => Navigator.of(context).pop(),
        child: const Text('OK'),
      ),
    ],
  ),
);

Use dialogContext to close the dialog and the outer context for anything that belongs to the page. If the dialog needs to trigger navigation after closing, close it first, then act on the outer context — guarded by context.mounted, since showDialog is itself awaited.

Reading the error messages

Once the model is in place, the three big ones decode instantly:

  • “No Scaffold widget found. X widgets require a Scaffold widget ancestor.” You looked up from a context at or above where the Scaffold was created. Add a Builder, or extract a child widget.
  • “No MaterialLocalizations found.” Almost always a showDialog or Navigator call from a context above MaterialApp — typically in the builder: of MaterialApp itself, or in a widget that is the app root. Move the call below MaterialApp.
  • dependOnInheritedWidgetOfExactType was called before initState completed.” An .of() call in initState or in a field initialiser. Move it to didChangeDependencies or build.
  • “Looking up a deactivated widget’s ancestor is unsafe.” A context used after its element was removed — the async-gap case, or a callback held past disposal. Guard with context.mounted, or capture the state object earlier.

FAQ

Can I store a BuildContext in a field and use it later?

Technically yes, practically no. The moment the element is unmounted the stored context is a liability, and nothing warns you. Capture the specific state object you need (NavigatorState, ScaffoldMessengerState) instead.

Is context the same object across rebuilds?

For the same widget in the same position, yes — the element persists while Widget.canUpdate keeps returning true. That is exactly the identity that keys control.

Why does Builder exist if it renders nothing?

To create an element, and therefore a context, one level below the current one. That is its entire job: giving .of() lookups a lower starting point without extracting a new widget class.

Is context.mounted enough, or do I need State.mounted?

Inside a State, either works; they check the same underlying element. context.mounted is the one available in a StatelessWidget callback, so it is the more generally applicable habit.

Does MediaQuery.of really cause that many rebuilds?

On a page with a text field, yes — every keyboard animation frame changes viewInsets, and every dependent rebuilds. Switching to MediaQuery.sizeOf or paddingOf where that is all you need is one of the cheapest performance fixes available.


The mechanics described here — the Element implements BuildContext relationship, the dependency registration in dependOnInheritedWidgetOfExactType, and the lint behaviour — are from the Flutter framework and Dart linter documentation linked above. Which patterns are worth adopting is my own judgement.


Originally published on FlutterCook. Read the latest version there — that copy is the one kept up to date.

Nhận xét

Bài đăng phổ biến từ blog này

5 concepts every Flutter dev should know

  Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

Announcing Flutter 2

  Phụ lục: Flutter on the web Flutter 2 on desktops, foldables, and embedded devices The growing Flutter ecosystem Dart: The secret sauce behind Flutter Flutter 2: Available now Hôm nay, chúng tôi sẽ công bố Flutter 2: một bản nâng cấp lớn cho Flutter cho phép các nhà phát triển tạo các ứng dụng đẹp, nhanh chóng và di động cho bất kỳ nền tảng nào. Với Flutter 2, bạn có thể sử dụng cùng một cơ sở mã để gửi các ứng dụng gốc cho năm hệ điều hành: IOS, Android, Windows, macOS và Linux; cũng như trải nghiệm web nhắm mục tiêu các trình duyệt như Chrome, Firefox, Safari hoặc Edge. Flutter thậm chí có thể được nhúng vào ô tô, TV và thiết bị gia dụng thông minh, mang đến trải nghiệm di động và lan tỏa nhất cho thế giới điện toán xung quanh. Mục tiêu của chúng tôi là thay đổi cơ bản cách các nhà phát triển nghĩ về việc xây dựng ứng dụng, bắt đầu không phải với nền tảng bạn đang nhắm mục tiêu mà là với trải nghiệm bạn muốn tạo. Flutter cho phép bạn tạo ra những trải nghiệm tuyệt đẹp trong đó ...