Loading...
Loading...
Enforces Flutter widget composition — extract named const Widget classes never `Widget _buildX()` methods, lean build() (no I/O/formatting/domain math, precompute in the ViewModel), dumb Views that watch one Notifier and route intents via ref.read, StatelessWidget by default with every controller disposed, lazy `.builder` lists, cheapest-widget choices (SizedBox/ColoredBox/Align over Container), a strict key policy (ValueKey for reorderable lists, never GlobalKey), gesture→visible-focusable-fallback wiring, plus structural layout — full-bleed background vs SafeArea content, computed cell sizing, the GridView cross/main-axis spacing trap, EdgeInsetsDirectional, and resizeToAvoidBottomInset/IME handling. Use when building or refactoring any screen or widget, splitting a large build() into components, writing GridView/ListView/LayoutBuilder/SafeArea/Scaffold, wiring onTap/onLongPress/Draggable, choosing a key or data class, or reviewing widget code in a diff.
npx skill4agent add zakariaf/flutter-skills widget-compositionconstreferences/rebuild-mechanics.mdElement.updateChildconst.selectreferences/structural-layout.mdSafeAreaGridViewresizeToAvoidBottomInsetreferences/widget-and-data-selection.mdscripts/check-widget-composition.shWidgetWidgetWidget _buildHeader()ElementElement.updateChildconstKeyfind.byTypeconstfinalconstprefer_const_constructorsprefer_const_constructors_in_immutablesdart fix --applybuild()dart3-idioms-and-coding-standardsbuild()build()build()build()DateTime.now()NumberFormatConsumerWidgetref.watch(oneNotifierProvider)ref.read(...notifier)StatelessWidgetConsumerWidgetStatefulWidgetConsumerStatefulWidgetAnimationControllerTextEditingControllerScrollControllerFocusNodedispose()StateAnimationControllerTextEditingControllerScrollControllerFocusNodecancel_subscriptionsclose_sinksListView.builderSliverListListView(children: items.map(...).toList())onTap: () => onSelect(item.id)Theme.of(context)ThemeExtensionEdgeInsetsDirectionalAlignmentDirectionalstartend// WRONG — helper method: rebuilds with parent, no const, no key, no isolation, untestable
Widget _buildProductCard(Product product) {
return Card(child: Text(product.name));
}
// RIGHT — widget class: const-constructible, keyable, independently rebuildable, testable
class ProductCard extends StatelessWidget {
const ProductCard({required this.name, required this.price, this.onTap, super.key});
final String name; // already-localized; the feature mapped Product -> name
final String price; // already-formatted; no currency math here
final VoidCallback? onTap;
Widget build(BuildContext context) {
final theme = Theme.of(context); // read theme in the leaf that uses it
return Card(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsetsDirectional.all(12), // logical, RTL-safe
child: Row(
children: [
Expanded(child: Text(name, style: theme.textTheme.titleMedium)),
Text(price, style: theme.textTheme.bodyMedium),
],
),
),
),
);
}
}// WRONG — domain math + formatting inside build()
Widget build(BuildContext context, WidgetRef ref) {
final order = ref.read(orderRepositoryProvider).find(id); // repo call in build!
final remaining = order.total - order.paid; // domain math in UI
final label = NumberFormat.currency().format(remaining); // formatting in UI
return Text(remaining <= 0 ? 'Done' : label);
}
// RIGHT — the Notifier exposes ready-to-render UI state; select narrows the rebuild
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(orderNotifierProvider(id).select((s) => s.balanceStatus));
return Text(status.label, style: status.isSettled ? _settledStyle : _dueStyle);
}ref.watch(p.select((s) => s.field))conststate-management-riverpodflutter-performance.selectonXMediaQueryThemebuild()SizedBoxContainerColoredBoxDecoratedBoxContainerAlignCenterRowSpacerreferences/widget-and-data-selection.mdconstrefElementStateWidgetState| Situation | Key |
|---|---|
| A row in a reorderable / filterable / variable list | |
| A fixed-order, fixed-length list; leaf stateless widgets | none — a key changes nothing |
| Swapping two widgets of the same type and needing to preserve state | a |
A widget that suddenly "wants" a | stop — that is a design smell, not a fix |
GlobalKeyreferences/rebuild-mechanics.md| Shape | Use |
|---|---|
| Persisted row | the drift-generated row class directly — it has |
| Ephemeral multi-return inside one layer | a record — |
| Hand-authored domain value / UI-state | |
| A joined shape no generator emits | hand-written |
equatable==Object.hash(...)references/widget-and-data-selection.mddart3-idioms-and-coding-standardsSafeAreaScaffold(
backgroundColor: Theme.of(context).colorScheme.surface, // full-bleed, behind bars
resizeToAvoidBottomInset: false, // a fixed grid must not reflow under the IME
body: SafeArea(
child: Padding(
padding: const EdgeInsetsDirectional.all(16), // logical inset, RTL-safe
child: child,
),
),
)GridViewcrossAxisSpacingmainAxisSpacingGridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 12, // COLUMN gap in a vertical grid
mainAxisSpacing: 16, // ROW gap
),
itemCount: items.length,
itemBuilder: (context, i) => ProductCard(name: items[i].name, price: items[i].price),
);MediaQuery.viewInsetsColumnreferences/structural-layout.mdaccessibility-as-codeMediaQuerytextScalerdesign-system-structureWidget _buildXxx()Elementbuild()SharedPreferencesbuild()initState()DateTime.now()ClockclockProviderstate-management-riverpodbuild()Statedispose()setState()setStateListView(children: hugeList.map(...).toList()).builderContainerGlobalKeyObjectKeyValueKey(id)EdgeInsetsDirectionaltextScalerWidget_buildX()constfinalbuild()DateTime.now()build()ConsumerWidgetref.read(...notifier)StatefulWidgetdispose().builderValueKey(id)GlobalKeySafeAreaGridViewtextScalerdart formatdart analyze --fatal-infosscripts/check-widget-composition.shflutter-architectureflutter-performance.selectRepaintBoundarystate-management-riverpoddesign-system-structureaccessibility-as-codeMediaQuerySemanticstextScaleri18n-rtl-l10nEdgeInsetsDirectionalAlignmentDirectionaldart3-idioms-and-coding-standardsadaptive-layoutforms-and-inputFormTextFormFieldwidget-golden-and-a11y-testingcustom-canvas-and-gesturesCustomPainterStatelessWidgetListView.builderEdgeInsetsDirectionalSafeAreaConsumerWidgetselect