Skip to content

Fix crash when errorBuilder is used with decoration containing errorText#183384

Closed
04cb wants to merge 1 commit into
flutter:mainfrom
04cb:fix/error-builder-copywith
Closed

Fix crash when errorBuilder is used with decoration containing errorText#183384
04cb wants to merge 1 commit into
flutter:mainfrom
04cb:fix/error-builder-copywith

Conversation

@04cb

@04cb 04cb commented Mar 9, 2026

Copy link
Copy Markdown

Fixes #183378. When using errorBuilder alongside a decoration that has errorText, both error and errorText end up non-null, triggering the assertion "Declaring both error and errorText is not supported." This happens because InputDecoration.copyWith() cannot clear fields (passing null means "keep existing value").

The fix explicitly creates a new InputDecoration with errorText set to null when errorBuilder is used, ensuring only one error display mechanism is active at a time.

Fixes #183378. When using errorBuilder alongside a decoration that has
errorText, both error and errorText end up non-null, triggering the
assertion "Declaring both error and errorText is not supported." This
happens because InputDecoration.copyWith() cannot clear fields (passing
null means "keep existing value").

The fix explicitly creates a new InputDecoration with errorText set to
null when errorBuilder is used, ensuring only one error display mechanism
is active at a time.
@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. f: material design flutter/packages/flutter/material repository. labels Mar 9, 2026

@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 correctly fixes a crash that occurs when errorBuilder is used with a decoration that also defines errorText. The approach of creating a new InputDecoration instance to explicitly nullify errorText is sound. However, this has been implemented in a very verbose way in both DropdownButtonFormField and TextFormField, leading to significant code duplication. My review includes suggestions to refactor this logic to improve maintainability.

Note: Security Review is unavailable for this PR.

Comment on lines +1875 to +1943
if (error != null) {
// When using errorBuilder, we need to explicitly clear errorText since
// copyWith cannot clear fields (null means "keep existing value").
effectiveDecoration = InputDecoration(
error: error,
errorText: null,
hintText: hintText,
icon: effectiveDecoration.icon,
iconColor: effectiveDecoration.iconColor,
label: effectiveDecoration.label,
labelText: effectiveDecoration.labelText,
labelStyle: effectiveDecoration.labelStyle,
floatingLabelStyle: effectiveDecoration.floatingLabelStyle,
helper: effectiveDecoration.helper,
helperText: effectiveDecoration.helperText,
helperStyle: effectiveDecoration.helperStyle,
helperMaxLines: effectiveDecoration.helperMaxLines,
hint: effectiveDecoration.hint,
hintStyle: effectiveDecoration.hintStyle,
hintTextDirection: effectiveDecoration.hintTextDirection,
hintFadeDuration: effectiveDecoration.hintFadeDuration,
hintMaxLines: effectiveDecoration.hintMaxLines,
maintainHintHeight: effectiveDecoration.maintainHintHeight,
maintainHintSize: effectiveDecoration.maintainHintSize,
maintainLabelSize: effectiveDecoration.maintainLabelSize,
errorStyle: effectiveDecoration.errorStyle,
errorMaxLines: effectiveDecoration.errorMaxLines,
floatingLabelBehavior: effectiveDecoration.floatingLabelBehavior,
floatingLabelAlignment: effectiveDecoration.floatingLabelAlignment,
isCollapsed: effectiveDecoration.isCollapsed,
isDense: effectiveDecoration.isDense,
contentPadding: effectiveDecoration.contentPadding,
prefixIcon: effectiveDecoration.prefixIcon,
prefix: effectiveDecoration.prefix,
prefixText: effectiveDecoration.prefixText,
prefixIconConstraints: effectiveDecoration.prefixIconConstraints,
prefixStyle: effectiveDecoration.prefixStyle,
prefixIconColor: effectiveDecoration.prefixIconColor,
suffixIcon: effectiveDecoration.suffixIcon,
suffix: effectiveDecoration.suffix,
suffixText: effectiveDecoration.suffixText,
suffixStyle: effectiveDecoration.suffixStyle,
suffixIconColor: effectiveDecoration.suffixIconColor,
suffixIconConstraints: effectiveDecoration.suffixIconConstraints,
counter: effectiveDecoration.counter,
counterText: effectiveDecoration.counterText,
counterStyle: effectiveDecoration.counterStyle,
filled: effectiveDecoration.filled,
fillColor: effectiveDecoration.fillColor,
focusColor: effectiveDecoration.focusColor,
hoverColor: effectiveDecoration.hoverColor,
errorBorder: effectiveDecoration.errorBorder,
focusedBorder: effectiveDecoration.focusedBorder,
focusedErrorBorder: effectiveDecoration.focusedErrorBorder,
disabledBorder: effectiveDecoration.disabledBorder,
enabledBorder: effectiveDecoration.enabledBorder,
border: effectiveDecoration.border,
enabled: effectiveDecoration.enabled,
semanticCounterText: effectiveDecoration.semanticCounterText,
alignLabelWithHint: effectiveDecoration.alignLabelWithHint,
constraints: effectiveDecoration.constraints,
visualDensity: effectiveDecoration.visualDensity,
);
} else {
effectiveDecoration = effectiveDecoration.copyWith(
errorText: errorText,
hintText: hintText,
);
}

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

While creating a new InputDecoration instance is the correct way to solve this issue (given that copyWith cannot nullify fields), this implementation is very verbose and hard to maintain. A nearly identical block of code has been added in text_form_field.dart, which introduces duplication.

To improve maintainability, consider extracting this logic into a shared helper function to avoid repeating this large constructor call.

Comment on lines +220 to +276
? InputDecoration(
error: errorBuilder(state.context, errorText),
errorText: null,
icon: effectiveDecoration.icon,
iconColor: effectiveDecoration.iconColor,
label: effectiveDecoration.label,
labelText: effectiveDecoration.labelText,
labelStyle: effectiveDecoration.labelStyle,
floatingLabelStyle: effectiveDecoration.floatingLabelStyle,
helper: effectiveDecoration.helper,
helperText: effectiveDecoration.helperText,
helperStyle: effectiveDecoration.helperStyle,
helperMaxLines: effectiveDecoration.helperMaxLines,
hintText: effectiveDecoration.hintText,
hint: effectiveDecoration.hint,
hintStyle: effectiveDecoration.hintStyle,
hintTextDirection: effectiveDecoration.hintTextDirection,
hintFadeDuration: effectiveDecoration.hintFadeDuration,
hintMaxLines: effectiveDecoration.hintMaxLines,
errorStyle: effectiveDecoration.errorStyle,
errorMaxLines: effectiveDecoration.errorMaxLines,
floatingLabelBehavior: effectiveDecoration.floatingLabelBehavior,
floatingLabelAlignment: effectiveDecoration.floatingLabelAlignment,
isCollapsed: effectiveDecoration.isCollapsed,
isDense: effectiveDecoration.isDense,
contentPadding: effectiveDecoration.contentPadding,
prefixIcon: effectiveDecoration.prefixIcon,
prefix: effectiveDecoration.prefix,
prefixText: effectiveDecoration.prefixText,
prefixIconConstraints: effectiveDecoration.prefixIconConstraints,
prefixStyle: effectiveDecoration.prefixStyle,
prefixIconColor: effectiveDecoration.prefixIconColor,
suffixIcon: effectiveDecoration.suffixIcon,
suffix: effectiveDecoration.suffix,
suffixText: effectiveDecoration.suffixText,
suffixStyle: effectiveDecoration.suffixStyle,
suffixIconColor: effectiveDecoration.suffixIconColor,
suffixIconConstraints: effectiveDecoration.suffixIconConstraints,
counter: effectiveDecoration.counter,
counterText: effectiveDecoration.counterText,
counterStyle: effectiveDecoration.counterStyle,
filled: effectiveDecoration.filled,
fillColor: effectiveDecoration.fillColor,
focusColor: effectiveDecoration.focusColor,
hoverColor: effectiveDecoration.hoverColor,
errorBorder: effectiveDecoration.errorBorder,
focusedBorder: effectiveDecoration.focusedBorder,
focusedErrorBorder: effectiveDecoration.focusedErrorBorder,
disabledBorder: effectiveDecoration.disabledBorder,
enabledBorder: effectiveDecoration.enabledBorder,
border: effectiveDecoration.border,
enabled: effectiveDecoration.enabled,
semanticCounterText: effectiveDecoration.semanticCounterText,
alignLabelWithHint: effectiveDecoration.alignLabelWithHint,
constraints: effectiveDecoration.constraints,
visualDensity: effectiveDecoration.visualDensity,
)

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 large block for creating a new InputDecoration is very similar to the one added in dropdown.dart. This duplication makes the code harder to maintain.

To improve this, consider refactoring this logic into a shared helper function that can be used in both places. This would centralize the verbose constructor call and make future updates to InputDecoration easier to manage.

@Hari-07

Hari-07 commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Could you update the description to match the PR Template?

A much easier fix would be to just add an assert on these widgets to make sure both are not non-null in the constructor. That's a pattern we follow in other widgets as well

You will also need to add tests that trigger the assert

errorText: errorText,
hintText: hintText,
);
if (error != null) {

@Mairramer Mairramer Mar 9, 2026

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 believe it’s possible to use merge instead of recreating everything.

@04cb 04cb closed this by deleting the head repository Mar 10, 2026
github-merge-queue Bot pushed a commit that referenced this pull request Mar 22, 2026
This PR fixes a framework crash that occurs when `errorBuilder` is used
alongside an `InputDecoration` that contains an `errorText`.

Because `InputDecoration.copyWith` ignores `null` values, attempting to
clear the `errorText` dynamically during the build phase leaves the
decoration with both properties defined, triggering a late assertion in
`InputDecorator`.

Following the architectural guidance provided by @Hari-07 in PR #183384,
this fix enforces mutual exclusivity at the constructor level. It adds
an `assert` to both `TextFormField` and `DropdownButtonFormField` to
ensure developers fail fast if both properties are provided.

Fixes #183378

I added the following tests to verify the assertions:
- `TextFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.
- `DropdownButtonFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.

---------

Co-authored-by: Hari07 <22373191+Hari-07@users.noreply.github.com>
mboetger pushed a commit to mboetger/flutter that referenced this pull request Mar 26, 2026
…#183901)

This PR fixes a framework crash that occurs when `errorBuilder` is used
alongside an `InputDecoration` that contains an `errorText`.

Because `InputDecoration.copyWith` ignores `null` values, attempting to
clear the `errorText` dynamically during the build phase leaves the
decoration with both properties defined, triggering a late assertion in
`InputDecorator`.

Following the architectural guidance provided by @Hari-07 in PR flutter#183384,
this fix enforces mutual exclusivity at the constructor level. It adds
an `assert` to both `TextFormField` and `DropdownButtonFormField` to
ensure developers fail fast if both properties are provided.

Fixes flutter#183378

I added the following tests to verify the assertions:
- `TextFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.
- `DropdownButtonFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.

---------

Co-authored-by: Hari07 <22373191+Hari-07@users.noreply.github.com>
ahmedsameha1 pushed a commit to ahmedsameha1/flutter that referenced this pull request Apr 14, 2026
…#183901)

This PR fixes a framework crash that occurs when `errorBuilder` is used
alongside an `InputDecoration` that contains an `errorText`.

Because `InputDecoration.copyWith` ignores `null` values, attempting to
clear the `errorText` dynamically during the build phase leaves the
decoration with both properties defined, triggering a late assertion in
`InputDecorator`.

Following the architectural guidance provided by @Hari-07 in PR flutter#183384,
this fix enforces mutual exclusivity at the constructor level. It adds
an `assert` to both `TextFormField` and `DropdownButtonFormField` to
ensure developers fail fast if both properties are provided.

Fixes flutter#183378

I added the following tests to verify the assertions:
- `TextFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.
- `DropdownButtonFormField` asserts when both `errorBuilder` and
`decoration.errorText` are provided.

---------

Co-authored-by: Hari07 <22373191+Hari-07@users.noreply.github.com>
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 f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants