44

I am looking for a better explanation on the benefit of TextEditingController over OnChanged event for a TextField.

My understanding is that onChanged's setState notifies all widgets of the change in state variable value. This way any widget (e.g. Text) can simply use the state variable and it will be notified of its changes.

My false hopes were TextEditingController would make it even simpler that I won't even need a state variable. Something like below:

import "package:flutter/material.dart";

class TestForm extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return TestFormState();
  }
}

class TestFormState extends State<TestForm> {
  //string myStateVariable = "";
  final ctrl = TextEditingController();

  @override
  Widget build(BuildContext context) {

    var tf = TextField(
      controller: ctrl,
    );

    var t = Text("Current value: " + ctrl.text);  // <<<<<<<<<<< false hope! doesnt work!

    var x = Column(children: <Widget>[tf,t],);

    return MaterialApp(home: Material(child: Scaffold(
      appBar: AppBar(title: Text("Test Form"),),
      body: x,
    )));
  }
}

Can anyone explain why TextEditingController or something similar cannot manage the state itself and notifies all consumers of change in state?

Thanks.

S2L
  • 1,746
  • 1
  • 16
  • 20

2 Answers2

29

You are just not setting state synchronously that's all. What onChanged does is exactly possible with this approach:

class _TestFormState extends State<TestForm> {
  late TextEditingController controller;

  @override
  void initState() {
    controller = TextEditingController()
      ..addListener(() {
        setState(() {});
      });
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: <Widget>[
        Text('Current Value: ${controller.text}'),
        TextField(
          controller: controller,
        ),
      ],
    );
  }
}

As you see, we have listener that setting state every time state of the controller changes. This is exactly what onChanged does.

So, about benefits, you can achieve everything with both approach, it's a subjective way.

About benefits: If you need to hold field values within Stream, onChanged is what you need. In other cases you may use controller.

Actually you won't need both in most of time in my opinion because TextFormField + Form within StatefulWidget is quite complete way to implement form pages. Checkout cookbook: https://flutter.dev/docs/cookbook/forms/validation

Mehmet Esen
  • 6,156
  • 3
  • 25
  • 44
  • 1
    @S2L I also added my thoughts, hope it helps. – Mehmet Esen Jul 05 '19 at 14:29
  • Question: how is variable binding handled e.g. Assume there are two variables updated in a setState of TextField, and both variables appears later in two different Text widgets. How are then variable changes propagated to the correct Text widget? (I am assuming that every Text widget gets only the event for variable that it uses). Who keeps the record of which widget needs which variable change event? – S2L Jul 05 '19 at 17:16
  • Also, is there a way to clear TextField without this controller? The only way I could locate online was controllerObject.Text = "" or clear method. Is there some way to do it without using Controller? – S2L Jul 05 '19 at 17:59
  • One problem that I ran into w/ using a `TextEditingController` is that `_formKey.currentState.reset()` doesn't reset fields that have a custom controller. – Alan Cabrera Apr 05 '20 at 14:47
  • @AlanCabrera are you sure, you are using key correctly? I would suggest you to open new question to get answers. – Mehmet Esen Apr 05 '20 at 16:09
  • @R.Shpd it's negligible, choose the way that makes you more productive, that's they key. – Mehmet Esen Jul 26 '20 at 10:42
12

TextEditingController actually is managing his own state, that's why you can see the input on the screen once you change it.

You have 2 problems here, the first is that you are not adding any listener to the TextEditingController, you are just asking "give me the current value" only when you build the widget, not "give me the value any time it changes". To achieve this you need to add a listener to the text controller and it will be called every time that the value change.

Try this :

  @override
  void initState() {
    super.initState();
    // Start listening to changes.
    ctrl.addListener(_printValue);
  }

  _printValue() {
    print("Value: ${ctrl.text}");
  }

This will work because print doesn't need to render anything on the screen but if you change it to return a widget it will not work either. That is the second problem, as you pointed out, your parent widget is not been rebuild when the value change, in this case you cannot avoid the setState (or other way to tell flutter that needs to rebuild the widget) when the value change because you need to rebuild the widget to view the change.

Another thing that ill like to point out is that TextEditingController is much powerful and it can be used for more things that just add notifiers to changes. For example if you want a button on other part of the screen that clear the text on a TextField you will need a TextEditingController binded to that field.

Hope it helps!

fvillalba
  • 963
  • 10
  • 18