'TextFormField "error message" breaks the shadow applied to the Text Field

I made a TextField builder and customized it and added a shadow to it using "Material" widget And when an error message is shown to the user in the app the TextField gets pushed up but the shadow itself stays Where it was (code and picture included below)

import 'package:flutter/material.dart';
import 'constants.dart';

class TextFieldBuilder extends StatefulWidget {
  TextFieldBuilder({
    Key key,
    this.icon,
    this.hint,
    this.obscure,
    this.height,
    this.fontSize,
    this.iconSize,
    this.fillColor,
    this.hintFontSize,
    this.iconColor,
    this.validatorFunction,
    this.textInputType,
    this.initialValue,
    this.onSavedFunc,
  }) : super(key: key);
  final IconData icon;
  final String hint;
  final bool obscure;
  final height, fontSize, iconSize, fillColor, hintFontSize;
  final iconColor;
  final validatorFunction;
  final textInputType;
  final initialValue;
  final onSavedFunc;
  @override
  _TextFieldBuilderState createState() => _TextFieldBuilderState();
}

class _TextFieldBuilderState extends State<TextFieldBuilder> {
  var _data;
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Material(
        color: Colors.transparent,
        borderRadius: BorderRadius.circular(9),
        child: Container(
          decoration:
              BoxDecoration(borderRadius: BorderRadius.circular(9), boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.15),
              blurRadius: 4,
              offset: Offset(1, 3),
            )
          ]),
          child: TextFormField(
            autovalidateMode: AutovalidateMode.onUserInteraction,
            initialValue: widget.initialValue ?? null,
            keyboardType: widget.textInputType ?? null,
            onSaved: widget.onSavedFunc ??
                (String value) {
                  _data = value.trim();
                  if (widget.hint != password &&
                      widget.hint != 'Confirm Password') {
                    print('${widget.hint} is $_data');
                  }
                },
            validator: widget.validatorFunction,
            style: TextStyle(
              color: Color(textColor),
              fontSize: widget.fontSize ?? 18,
            ),
            obscureText: widget.obscure ?? false,
            decoration: InputDecoration(
              contentPadding: EdgeInsets.symmetric(vertical: 1.0),
              prefixIcon: Icon(
                widget.icon ?? Icons.error,
                size: widget.iconSize ?? 35,
                color: widget.iconColor ?? Color(iconColor),
              ),
              filled: true,
              fillColor: Color(textFieldColor),
              hintText: widget.hint ?? placeholder,
              hintStyle: TextStyle(
                color: Color(textColor),
                fontSize: widget.fontSize ?? 18,
                height: widget.height ?? 0.9,
              ),
              border: OutlineInputBorder(
                borderSide: BorderSide.none,
                borderRadius: BorderRadius.circular(9),
              ),
              focusColor: Color(textFieldColor),
            ),
          ),
        ),
      ),
    );
  }
}

I tried wrapping both widgets with a container but it did not seem to help enter image description here



Solution 1:[1]

Combine your TextField with a Text and place both inside a Column. You will be able to replicate the error display without messing up the shadow. Remember to hide the default error message as well.

You can use the elevation and shadowColor property of Material to save some code lines btw. Something like:

Material(
    color: Colors.transparent,
    borderRadius: BorderRadius.circular(9),
    elevation: 4,
    shadowColor: Colors.black.withOpacity(0.15),
    // ... other lines
);

Example code:

// ... other lines

class _TextFieldBuilderState extends State<TextFieldBuilder> {
  var _data;
  String _errorText = '';

  @override
  Widget build(BuildContext context) {
    return Container(

      //!! Place both Widgets inside a Column
      child: Column( 
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Material(
            color: Colors.transparent,
            borderRadius: BorderRadius.circular(9),
            child: Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(9),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.15),
                      blurRadius: 4,
                      offset: Offset(1, 3),
                    )
                  ]),
              child: TextFormField(
                autovalidateMode: AutovalidateMode.onUserInteraction,
                initialValue: widget.initialValue ?? null,
                keyboardType: widget.textInputType ?? null,
                onSaved: widget.onSavedFunc ??
                    (String value) {
                      _data = value.trim();
                      if (widget.hint != password &&
                          widget.hint != 'Confirm Password') {
                        print('${widget.hint} is $_data');
                      }
                    },

                //!! Set the errorText value here, the Future delayed is to avoid calling [setState] during [build]
                validator: (value) {
                  Future.delayed(Duration.zero, () { 
                  // calling [setState] during [build]
                    setState(() {
                      _errorText = widget.validatorFunction(value) ?? '';
                    });
                  });
                  return _errorText;
                },
                style: TextStyle(
                  color: Color(textColor),
                  fontSize: widget.fontSize ?? 18,
                ),
                obscureText: widget.obscure ?? false,
                decoration: InputDecoration(
                  contentPadding: EdgeInsets.symmetric(vertical: 1.0),
                  prefixIcon: Icon(
                    widget.icon ?? Icons.error,
                    size: widget.iconSize ?? 35,
                    color: widget.iconColor ?? Color(iconColor),
                  ),
                  filled: true,
                  fillColor: Color(textFieldColor),

                  //!! Hide the default error message here
                  errorStyle: TextStyle(fontSize: 0), 
                  hintText: widget.hint ?? placeholder,
                  hintStyle: TextStyle(
                    color: Color(textColor),
                    fontSize: widget.fontSize ?? 18,
                    height: widget.height ?? 0.9,
                  ),
                  border: OutlineInputBorder(
                    borderSide: BorderSide.none,
                    borderRadius: BorderRadius.circular(9),
                  ),
                  focusColor: Color(textFieldColor),
                ),
              ),
            ),
          ),

          //!! Display the error message here
          if (_errorText.isNotEmpty)
            Text(
              _errorText,
              style: TextStyle(color: Colors.red),
            )
        ],
      ),
    );
  }
}

Solution 2:[2]

  • Material color must be transparent -> color: Colors.transparent, Like @Bach Said,
  • TextFormField error style font size should be 0 as @Bach said too, and the height property should be 1 -> errorStyle: TextStyle(fontSize: 0, height: 1)

and show the error message in another widget

Solution 3:[3]

You can use package https://pub.dev/packages/control_style or just implement class DecoratedInputBorder from this package in your code. You can copy the class from the example attached to the answer. This class is needed to wrap OutlineInputBorder with it. The class takes the border of a text field and draws a shadow to the border above control. To create an illusion that the shadow is behind the control, the shadow's area above the control gets cut off.

This approch is suitable for customizing text fields both globally through theme property of MateralApp and locally for separate TextField through decoration property of the input.

Customization through MateralApp:

    MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        inputDecorationTheme: InputDecorationTheme(
          border: DecoratedInputBorder(
            child: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8),
            ),
            shadow: const [
              BoxShadow(
                color: Colors.blue,
                blurRadius: 12,
              )
            ],
          ),
        ),
      ),
    );

TextField customization:

    TextField(
      decoration: InputDecoration(
          border: DecoratedInputBorder(
        child: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8),
        ),
        shadow: const [
          BoxShadow(
            color: Colors.blue,
            blurRadius: 12,
          )
        ],
      )),
    );

enter image description here

Example: Open DartPad

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 J. almadhaji
Solution 3