Creating a reactive TextField in Flutter

Search for a command to run...

No comments yet. Be the first to comment.
As Flutter developers, we love the efficiency of writing code once and deploying it everywhere. But when it comes to the very final step of launching or updating an app, generating App Store and Play

If you're looking for a streamlined way to distribute your Android test builds using Firebase App Distribution (FAD) with the modern Android App Bundle (.aab) format, you might want to pause. While the promise of using AABs via FAD—leveraging the Goo...

Last month, I set myself a new challenge: to build and release a small app from scratch within a month. This decision came after a realization I had while working on my previous project. That project had so many uncertainties and things left to do th...

Using Flutter's CustomPaint to create futuristic looking widgets.

Last year, I wrote two articles about theming in Flutter, sharing my frustrations with ThemeData (part 1 & part 2). While ThemeData is powerful, its extensive properties can be overwhelming, especially when many of them are rarely used. After experim...

In this article, I will show you how you can very easily create a reactive TextField in Flutter.
A few months ago, while working on a client project, I was building multiple pages in a row which all contained some kind of form, like a login-, registration- and personal details form.
I soon got annoyed with all sorts of things related to building these forms...
Each page had to be Stateful (StatefulWidget).
All the TextEditingControllers and FocusNodes had to be created manually.
We had to think about disposing these TextEditingControllers and FocusNodes.
The TextEditingControllers had to be 'connected' with our state management.
Validation ended up in several places, depending on if the validation was client- or server-sided.
All of this resulted in a painful amount of boilerplate code.
And I was not having it...
A bit later, I had to create a form for my Scavenger Hunt app pet project. This form had only one TextField, but it also contained a Slider.
I realized the Slider was way simpler to work with. Its most important properties are: value and onChanged.
With value you tell the Slider its current value, and with onChanged you receive the value the user changed the slider to. No hassle of dealing with controllers.
So I was thinking, what if I could make a TextField that works the same way?
After playing around with some ideas, I ended up with something that can be used like this:
ReactiveTextField(
text: state.counter.toString(),
onChange: context.read<CounterCubit>().setValue,
error: state.error,
),
As you can see, there are no controllers to deal with. Its properties can be directly connected with the state management solution of your choosing.
Below you can see its implementation.
class ReactiveTextField extends StatefulWidget {
const ReactiveTextField({
super.key,
required this.text,
required this.onChange,
this.error,
});
final String text;
final void Function(String text) onChange;
final String? error;
@override
State<ReactiveTextField> createState() => _ReactiveTextFieldState();
}
class _ReactiveTextFieldState extends State<ReactiveTextField> {
// ReactiveTextField will hold onto its own TextEditingController and Focusnode,
// which you otherwise had to create and maintain yourself.
final _controller = TextEditingController();
final _focusNode = FocusNode();
@override
void initState() {
super.initState();
_controller.text = widget.text;
}
@override
void didUpdateWidget(covariant ReactiveTextField oldWidget) {
super.didUpdateWidget(oldWidget);
// This is an important part!
// Without this check, there will be an infinite loop!
if (widget.text != _controller.text) {
_controller.text = widget.text;
// We remove focus, because the value has been set
// from a different source.
_focusNode.unfocus();
}
}
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// ReactiveTextField simply wraps a 'normal' TextField.
return TextField(
controller: _controller,
focusNode: _focusNode,
onChanged: widget.onChange,
decoration: InputDecoration(
errorText: widget.error,
),
);
}
}
As you can see, it is very simple. The most 'tricky' part here is the didUpdateWidget section, which is needed to prevent an infinite update loop from happening.
This ReactiveTextField widget will manage its controller so the parent can remain a StatelessWidget. All you have to do as the user is set the current value and listen to the updates, THAT IS IT!
Check out this dartpad link for a full example!
Let me know what you think!