Skip to content

avoid_returning_widgets

v0.4.0WarningConfigurableWidget Best Practices

Flags a function, method or getter whose return type is a Widget.

A _buildHeader() helper builds its subtree inside the caller’s build, so Flutter sees one element where you wrote two. It cannot skip the header when only the footer changed, cannot give it a const constructor, and cannot show it separately in the devtools tree. A _Header widget class costs the same lines and gets all three.

This rule is in the recommended preset, so it is on with preset: recommended and every preset above it.

See also: Flutter performance best practices

Methods and getters that return a widget are both reported:

class MyScreen extends StatelessWidget {
const MyScreen({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [_buildHeader(), _body]);
}
Widget _buildHeader() => const Text('Header');
Widget get _body => const Text('Body');
}
class MyScreen extends StatelessWidget {
const MyScreen({super.key});
@override
Widget build(BuildContext context) {
return const Column(children: [_Header(), _Body()]);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) => const Text('Header');
}
class _Body extends StatelessWidget {
const _Body();
@override
Widget build(BuildContext context) => const Text('Body');
}

Handing the declaration to a builder: rather than calling it means the framework invokes it at its own point in the tree — that is a widget class in all but spelling, so it is not reported:

class Page extends StatelessWidget {
const Page({super.key});
@override
Widget build(BuildContext context) => Builder(builder: _row); // not reported
Widget _row(BuildContext context) => const Text('Row');
}

The exemption is for tear-offs only. Column(children: [_row(context)]) calls the same method inline and is still reported.

A getter is never treated as a tear-off: => _body reads it rather than tearing it off, so a bare reference to a Widget-returning getter is the inline build this rule targets.

@swidget, @hwidget, @hcwidget and @FunctionalWidget generate a real widget class, so the rewrite this rule asks for has already happened:

@swidget
Widget productTile(BuildContext context, String name) => Text(name); // not reported

Add your own generator’s annotation with additional_ignored_annotations:

many_lints.yaml
rules:
avoid_returning_widgets:
additional_ignored_annotations: [widgetFactory]

Widget? usually means “render nothing”, which is not the helper-method pattern. It is reported by default; opt out with allow_nullable:

many_lints.yaml
rules:
avoid_returning_widgets:
allow_nullable: true
// Not reported under `allow_nullable: true`
Widget? _badgeOrNothing() => count == 0 ? null : Text('$count');

A build() override is never reported — it is the standard way to build a widget.

The tear-off exemption matches on the name, not the resolved element. Two declarations in one file sharing a name means torn off one exempts both. That is deliberate: a false exemption is far cheaper than falsely reporting a legitimate builder callback.

analysis_options.yaml
many_lints:
rules:
avoid_returning_widgets:
ignored_names: [buildLeading]
additional_ignored_annotations: [widgetFactory]
allow_nullable: true
Option Type Default Description
ignored_names list of strings [] Method and function names never reported
ignored_annotations list of strings [FunctionalWidget, swidget, hwidget, hcwidget] Annotation names that exempt a member (written without @). Replaces the defaults; use additional_ignored_annotations to extend them
additional_ignored_annotations list of strings [] Annotation names to exempt in addition to the defaults
allow_nullable bool false Exempt nullable returns (Widget?), where null usually means “render nothing”
many_lints.yaml
rules:
avoid_returning_widgets: false

To keep the rule on but skip certain paths, use per-rule exclude.