Sitelet https://github.com/flutter/flutter/pull/188939
Skip to content

Explain why build methods must not have side effects - #188939

Open
theprantadutta wants to merge 6 commits into
flutter:masterfrom
theprantadutta:docs/no-side-effects-in-build
Open

theprantadutta wants to merge 6 commits into
flutter:masterfrom
theprantadutta:docs/no-side-effects-in-build

Conversation

@theprantadutta

Copy link
Copy Markdown
Contributor

The docs for TextEditingController.text, StatelessWidget.build, and State.build all state that they must not be set / must not have side effects during build, but none of them explained why ΓÇö which is what #46728 asked for, and Hixie agreed upthread ("We can fix it as part of this issue"). I proposed this scope on the issue on May 13 and there have been no objections since.

This PR expands all three doc comments to explain the mechanism:

  • TextEditingController.text: listeners are typically used to rebuild widgets (attached text fields rebuild their content, other listeners often call setState); the framework does not allow widgets to be marked as needing to build during the build/layout/paint phases and throws when a listener attempts it; and setting the text from a build method risks rebuilding in a loop.
  • StatelessWidget.build / State.build: expands the single "should not have any side effects beyond building a widget" sentence into general guidance ΓÇö why side effects in build are repeated at unpredictable times, why triggering rebuilds from build is forbidden, and where such logic belongs instead (State.initState, State.didUpdateWidget, event handlers). The shared text lives in a {@template} on StatelessWidget.build so the two docs stay in sync.

Docs-only change, no behavior changes ΓÇö requesting a test exemption.

Fixes #46728

Pre-launch Checklist

@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. labels Jul 3, 2026
@theprantadutta
theprantadutta marked this pull request as ready for review July 21, 2026 14:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the documentation in editable_text.dart and framework.dart to clarify the side effects of setting TextEditingController.text and executing build methods, introducing a reusable template for build method side-effect warnings. The review feedback suggests expanding the documentation for TextEditingController.text to warn about potential infinite loops when modifying the text property from within its own listener, providing a code snippet to illustrate a safe pattern.

Comment on lines +281 to +282
/// build that sets the text again. This property can be set from a listener
/// added to this [TextEditingController].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This statement, while correct, can be a bit of a footgun. A common mistake is to set the controller's text from within its own listener, which can easily lead to an infinite loop of notifications.

Since this whole documentation block is about preventing unexpected behavior, it would be beneficial to add a word of caution here. I'd suggest separating this point into its own paragraph and expanding on the potential pitfalls and how to avoid them.

  /// build that sets the text again.
  ///
  /// This property can be set from a listener added to this [TextEditingController].
  /// When doing so, care should be taken to avoid infinite loops. A listener that
  /// sets text will be called again, so a common pattern is to first remove the
  /// listener, then set the text, and then re-add the listener.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — added a paragraph in 1ffa759 warning about the listener-loop footgun and suggesting the remove-set-re-add pattern to avoid it.

@Renzo-Olivares
Renzo-Olivares self-requested a review July 30, 2026 20:37

@LongCatIsLooong LongCatIsLooong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing. But the explanation feels way too lengthy which makes it look a bit daunting.

/// [TextEditingController], but take care to avoid an infinite loop: setting
/// the text notifies listeners, so a listener that sets the text will be
/// triggered again. A common way to avoid this is to remove the listener
/// before setting the text and re-add it afterward.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the explanation is too lengthy. Also using this method in production code is already discouraged:

/// This setter is typically only used in tests, as it resets the cursor

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed it down in 613979d. The setter is already documented as "typically only used in tests", so I've cut the explanation to the part that isn't obvious from that — that setting it notifies listeners, and so it must not happen during build/layout/paint. The infinite-loop paragraph is gone; it was over-explaining a setter that production code shouldn't be reaching for anyway.

/// any side effects beyond building a widget. The framework decides when and
/// how often to call it, so any other work done here (mutating state outside
/// the widget, notifying listeners, starting asynchronous operations, and so
/// on) would be repeated at unpredictable times, possibly as often as once

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like this is describing idempotency instead of being "side-effect-free" (it's a bit pedantic but I think idempotency is the more accurate right term here?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and it's not pedantic — idempotency is the accurate constraint. "No side effects" describes the symptom, but the actual requirement is that the framework may call build arbitrarily often, so calling it repeatedly has to have the same effect as calling it once. Reworded to lead with that in 613979d, which also let me drop the enumeration of side effects and shorten the whole block.

@LongCatIsLooong LongCatIsLooong added the waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds label Aug 7, 2026
@github-actions github-actions Bot removed the waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds label Aug 12, 2026
@theprantadutta

Copy link
Copy Markdown
Contributor Author

Rebased onto master in 63acd5d — this had picked up a conflict because master edited the same TextEditingController.text dartdoc block in the meantime.

Resolved by keeping both sides rather than either one:

  • my trimmed explanation of why it must not be set during build/layout/paint (the shortened version from your last round)
  • master's newly added Setting this does not run [TextInputFormatter]s. Apply them manually if needed.
  • and I put back the pre-existing This property can be set from a listener added to this [TextEditingController]., which my earlier trim had dropped — it isn't mine to remove, and without it the paragraph reads as if listeners can't set the value at all.

Both files analyze clean and are dart format clean. No other changes.

/// this value should only be set between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases. This property can
/// be set from a listener added to this [TextEditingController].
/// that they need to update (it calls [notifyListeners]), so it should only

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additions here don't add too much value, I recommend swapping them out with a reference to the build docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — done in b46d414. I've reverted the prose here to exactly what was on master and replaced my additions with a pointer instead:

/// See also:
///
///  * [StatelessWidget.build], which explains why a build method must not
///    have side effects such as setting this value.

That keeps the explanation in one place (the {@template flutter.widgets.StatelessWidget.build.noSideEffects} block this PR adds in framework.dart, which State.build already pulls in via {@macro}) rather than restating a shortened version of it here. The editable_text.dart side of the diff is now just those five lines.

I also merged master in — the branch had drifted 186 commits and was carrying a stale .ci.yaml, which was enough to fail presubmit on a sibling PR of mine.

@justinmc justinmc added the CICD Run CI/CD label Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TextEditingController.text docs miss a reason

4 participants