Add Error Shake Effect to TextFields | Flutter

Ali Talha Çoban
4 min readMar 1, 2023

--

In modern user interfaces, providing feedback to the user is a crucial aspect of the user experience. In Flutter, the TextField widget is commonly used for entering and editing text. To enhance the user experience, it’s often useful to add an error shake effect to the TextField when the user inputs an invalid value. In this article, I will guide you through the process of creating an error shake effect for the TextField widget in Flutter. We’ll cover the basics of adding animations to widgets in Flutter and explain how to trigger the animation when an error occurs. By the end of this article, you’ll have a solid understanding of how to implement this effect in your own Flutter apps.

Here’s a step-by-step overview of what we will do in this article;

  • Createing AnimationController class
  • Create an subclass named CustomShakeWidget to shake our widgets
  • Create a TextField where we’ll be added our error shake animation
  • Create a Button to click to trigger our TextField

At the end of this article you can find the whole source code.

AnimationController

In brief, AnimationController is a Flutter widget that controls the progression of an Animation. It provides means to start, stop, and reverse the animation, and to control its duration, speed, and repetition. The AnimationController is typically used in combination with other widgets, such as AnimatedBuilder, to create animations that are triggered by user interactions, or that are triggered by changes in other parts of an application’s state.

abstract class AnimationControllerState<T extends StatefulWidget>
extends State<T> with SingleTickerProviderStateMixin {
AnimationControllerState(this.animationDuration);

final Duration animationDuration;
late final animationController =
AnimationController(vsync: this, duration: animationDuration);

@override
void dispose() {
animationController.dispose();
super.dispose();
}
}

CustomShakeWidget

class ShakeWidget extends StatefulWidget {
const ShakeWidget({
Key? key,
required this.child,
required this.shakeOffset,
this.shakeCount = 3,
this.shakeDuration = const Duration(milliseconds: 400),
}) : super(key: key);
final Widget child;
final double shakeOffset;
final int shakeCount;
final Duration shakeDuration;

@override
ShakeWidgetState createState() => ShakeWidgetState(shakeDuration);
}

This custom widget class takes some parameters which are ;

  • child → This property is used for the widget that we want to shake.
  • shakeOffset → This property is used for the shake distance of the widget.
  • shakeCount → This property is used for how many times our widget should shake
  • shakeDuration → This property is used for the Shake duration. It represents a difference from one point in time to another.

CustomShakeWidgetState

class ShakeWidgetState extends AnimationControllerState<ShakeWidget> {
ShakeWidgetState(Duration duration) : super(duration);

@override
void initState() {
super.initState();
animationController.addStatusListener(_updateAnimationStatus);
}

@override
void dispose() {
animationController.removeStatusListener(_updateAnimationStatus);
super.dispose();
}

void _updateAnimationStatus(AnimationStatus status) {
if (status == AnimationStatus.completed) {
animationController.reset();
}
}

void shakeWidget() {
animationController.forward();
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
child: widget.child,
builder: (context, child) {
final sineValue =
sin(widget.shakeCount * 2 * pi * animationController.value);
return Transform.translate(
offset: Offset(sineValue * widget.shakeOffset, 0),
child: child,
);
},
);
}
}

shakeWidget() → to start the animation. Note that this method must be public to be called from outside

_updateAnimationStatus() → to reset the animationController when the animation is complete

About dispose() function

Releasing the resources is important in Flutter because it prevents memory leaks and ensures that the resources are available for garbage collection. The dispose method should be called in the dispose method of the widget that uses the animation.

main.dart

TextField and ElevatedButton

Let’s create our TextField and FloatingActionButton widget. The button is to trigger to TextField error and so the button shake it.

Before we create our TextField and ElevatedButton, we need to define some necessary variables which are;

// Controller object of our TextField 
var _controllerTextField = TextEditingController();

// Boolean variable to check for errors
bool isTextFieldError = false;

// GlobalKey object for ShakeWidgetState class
final textFieldErrorShakeKey = GlobalKey<ShakeWidgetState>();

Here is the TextField code ;

 Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextField(
maxLines: 1,
onChanged: (text) {
setState(() {
_isTextFieldError = (text.isEmpty);
});
},
controller: _controllerTextField,
decoration: InputDecoration(
labelText: "Input your data",
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide:
BorderSide(color: Colors.greenAccent, width: 2.0),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide:
BorderSide(color: Colors.lightBlue, width: 1.0),
),
hintText: "Input your data",
errorText: _isTextFieldError
? "Title field cannot be left empty" // Error message
: null),
),
),

And ElevatedButton;

ElevatedButton(
onPressed: () async {
if (_controllerTextField.text.isEmpty) {

// do anything when it gives an error

setState(() {

// update error status
isTextFieldError= true;

textFieldErrorShakeKey.currentState?.shakeWidget();
});
} else {

// do anything when it doesn't give an error

setState(() {

// update error status
isTextFieldError= true;

});

}
},
child: Text('Submit Data'),
),

In this step, if we click on the button, our TextField will give an error, but will not be shaken. To shake it, we need to wrap it with our ShakeWidget as follows

Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ShakeWidget(
key: _textFieldErrorShakeKey,
shakeCount: 3,
shakeOffset: 10,
shakeDuration: Duration(milliseconds: 500),
child: TextField(...)
),
),

Outcome

Here is the whole main.dart file

So there you have it, a simple and effective way to add an error shake effect to TextFields in Flutter. Thank you for taking the time to read this article, and I hope you found it helpful.

You can also contact me through the following accounts;

--

--

Ali Talha Çoban
Ali Talha Çoban

Written by Ali Talha Çoban

Backend Developer | Node.js | Spring Boot | PostgreSQL | MongoDB | MySQL

Responses (1)