Flutter / Dart, widgets, and local ownership / Lesson 2
Widget descriptions, composition, and context
Build small immutable descriptions and locate inherited services through the correct tree position.
On this page
Lesson goals
- Describe the interface rather than mutating it
- Give a reusable widget a meaningful contract
- Pass inputs explicitly
Describe the interface rather than mutating it
A widget is an immutable description of part of an interface. Flutter reconciles successive descriptions with persistent elements and rendering objects. Calling build is therefore not the same as constructing a new native screen or erasing every existing state object. Describe the desired result for current inputs, then let the framework coordinate updates. A build method can run frequently and should avoid sending requests, changing application data, or registering listeners. Such actions need their own lifecycle or event owner. This distinction keeps a visual rebuild from accidentally becoming an additional business operation.
Give a reusable widget a meaningful contract
Extract a widget when it names a responsibility, isolates a meaningful visual subtree, or clarifies inputs. A component that accepts a label and a count is easier to review than one reaching into a global map for undocumented values. Constructor parameters should express required information and sensible defaults. Keep business interpretation outside decorative wrappers: a colored box should not secretly decide whether a reservation is paid. Composition also preserves testing seams because the child can be rendered with synthetic values. Avoid turning every padding expression into an abstraction whose name conveys less than the original code.
Pass inputs explicitly
The first fragment defines a small immutable counter summary. The parent owns the number; the child only describes its presentation. The key parameter is forwarded so callers can preserve identity when the widget participates in a changing collection. const is available when constructor arguments are compile-time constants; it does not freeze an external service or prevent every parent rebuild. The output is a predicted visible label, assuming the widget is placed under a Flutter application host providing text direction and Material conventions. The fragment is not a complete main.dart application.
import 'package:flutter/material.dart';
class SeatSummary extends StatelessWidget {
const SeatSummary({super.key, required this.available});
final int available;
@override
Widget build(BuildContext context) => Text('Seats: $available');
}
// Parent expression: const SeatSummary(available: 4)
Seats: 4
The final field is a read-only input. Updating the parent input creates a new description for the same logical position.
Read inherited values below their provider
BuildContext represents a location in the element tree. Theme.of and similar lookups search ancestors of that location; they cannot see a provider that the same build method has only placed below it. The second fragment uses Builder to obtain a descendant context inside a local Theme. Naming the inner context helps make the relationship visible. An inherited lookup is neither a global variable nor a permanent capability detached from the tree. If a widget moves or dependencies change, the framework may rebuild dependent descriptions so they can read the current inherited information.
Widget localPanel() => Theme(
data: ThemeData(colorSchemeSeed: Colors.teal),
child: Builder(builder: (innerContext) {
final colors = Theme.of(innerContext).colorScheme;
return ColoredBox(color: colors.primaryContainer,
child: const Text('Local panel'));
}),
);
A panel uses the local teal-derived theme.
Builder creates the descendant location needed for Theme.of. Exact generated color values depend on the selected theme configuration.
Keep semantic meaning through composition
A visual row containing an icon and text is not automatically an accessible action. Use a suitable control such as a button when activation is intended, and keep a meaningful label when the icon alone is ambiguous. Theme and composition improve consistency but do not establish adequate contrast, target size, or focus behavior by themselves. Test long labels and right-to-left direction early because these reveal assumptions hidden by short English strings. Widget reuse is strongest when it carries both behavior and meaning, rather than only a copied arrangement of colored shapes.
Review ownership at the tree boundary
A widget accepting a callback should document what information it sends and whether repeated activation is allowed. The parent remains responsible for domain state, while the child remains responsible for presenting the interaction correctly. Do not cache BuildContext in a long-lived repository or retain it as an alternative to explicit dependencies. Infrastructure should not need to know which button created a request. This separation lets the same state drive another layout without rewriting data access and helps tests distinguish an incorrect rendered value from an incorrect source value.
Common errors and fixes
Theme.of reads the outer theme
- Why it happens
- The context belongs above the newly returned Theme.
- How to fix it
- Use a child widget or Builder below that Theme.
- Prevent it next time
- Draw provider and consumer positions when debugging inherited values.
A rebuild starts duplicate work
- Why it happens
- build contains a request or subscription.
- How to fix it
- Move the operation to the owner of the event or lifecycle.
- Prevent it next time
- Keep rendering a deterministic description.
An icon responds to touch but is unclear to assistive technology
- Why it happens
- A visual gesture wrapper lacks control semantics or a label.
- How to fix it
- Use a semantic button with an informative tooltip or label.
- Prevent it next time
- Review focus and semantics alongside appearance.
Check your understanding
1. Where should the available seat number be owned if two summaries display it?
Show worked solution
Place the authoritative value in their nearest suitable common owner, or a dedicated application state owner when its lifetime is broader. Pass the same value into both summaries. Keeping two independently mutable copies would require error-prone synchronization.
2. Why is a const widget not a general performance guarantee?
Show worked solution
It can enable reuse of an immutable description, but layout, painting, inherited dependencies, and other subtrees still have costs. Profile an actual interaction before changing boundaries, and preserve correctness rather than scattering const as a substitute for measurement.
Key takeaways
- Describe the interface rather than mutating it
- Review ownership at the tree boundary