TextEditingController as State Management

Brunno Marques
Flutter Brasil
Published in
2 min readMar 29, 2024

If you ever wanted to show an icon, a text or a button that somehow depends on the value of the TextField to be reactive, you might have used the onChange function to call for a setState o enabling a Valuenotifier named showIconNotifier. While this approach can work just fine, and depending on the situation, it might be even a better solution, but consider using the controller itself as the state management as it would be the direct way to apply the reactivity.

In Practice

In the code block below, there is a simple snippet to show a password validation example, it calls for a custom function named `validatePassword` as condition to the Visibility.

TextField(
controller: passwordController,
autofillHints: const [AutofillHints.password],
obscureText: true,
decoration: const InputDecoration(
labelText: 'Enter your Password',
),
onChanged: (value) {
setState(() {});
},
),
Row(
children: [
Visibility(
visible: validatePassword(passwordController.text),
replacement: Icon(
Icons.close,
color: Theme.of(context).colorScheme.primary,
),
child: Icon(
Icons.check,
color: Theme.of(context).colorScheme.error,
),
),
const SizedBox(
width: 8,
),
const Text('The password must be at least 6 characters long.'),
],
)

While in this scenario, I believe to be the best way to validate it, but as the application grows and the chain of condition increases, you might be reluctant to stay with this solution, and there are a bunch of alternatives, but I am here to suggest the usage of the controller.

TextField(
controller: passwordController,
autofillHints: const [AutofillHints.password],
obscureText: true,
decoration: const InputDecoration(
labelText: 'Enter your Password',
),
),
ListenableBuilder(
listenable: passwordController,
builder: (context, _) {
return Row(
children: [
Visibility(
visible: validatePassword(passwordController.text),
replacement: Icon(
Icons.close,
color: Theme.of(context).colorScheme.primary,
),
child: Icon(
Icons.check,
color: Theme.of(context).colorScheme.error,
),
),
const SizedBox(
width: 8,
),
const Text('The password must be at least 6 characters long.'),
],
);
}
)

The vantages of this approach might not be clear in first sight, because it increases the Widget Tree with the ListenableBuilder and removes the simple setState function. But like many good practices, they aim to make the code better in the long term, so here some pros of using it

Scalable: As the application grows, you might need to implement more validations and can introduce then inside the ListenableBuilder as you wish.

Direct Validation: In case of using ValueNotifier like a isPasswordValidatedNotifier to enable a button, it will behave like a movable Part that will be a weak point in the code structure possibly leading to bugs.

Rebuilds: By listening to the controller, you can avoid unwanted calls of the build method, while this won’t affect the performance drastically because Flutter is extremely optimized, it can lead to unwanted behavior if you have children's that uses a StateFull life cycle.

I wanted to share this hint because I solved an issue in my work today using a similar approach, and I wish that many other developers make a good usage of the good practices in Flutter.

You can visit ours awesome community in the Discord https://discord.gg/TzREFB8Rn8

--

--