Flutter: How can I retrieve the text from a TextEditingController in a custom StatefulWidget?

Viewed 533

I am trying to make a custom stateful widget work in flutter. It is called LetterTextForm, and contains some layout customization and a TextField.

I want it to be easy to retrieve the text that is entered into the TextField of the widget, with some kind of getter method in the widget body that returns TextEditingController.text. The problem is, when I make the TextEditingController controller a final variable like this:

class LetterTextForm extends StatefulWidget {

  LetterTextForm({@required this.label, this.prefixIcon});
  final Widget prefixIcon;
  final String label;
  final TextEditingController controller = TextEditingController();
  String get textEntered => controller.text;


  @override
  _LetterTextFormState createState() => _LetterTextFormState();
}

class _LetterTextFormState extends State<LetterTextForm> {
  

  @override
  void initState() {
    super.initState();
  }

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


  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: widget.controller,
        decoration: InputDecoration(
          prefixIcon: widget.prefixIcon,
          labelText: widget.label,
        )
    );
  }
}


the following error is thrown: "A TextEditingController was used after being disposed."

I thought maybe the problem was that the controller shouldn't be final, so I moved the controller into the state class and made a function in the state class, textCallback, that modifies a non-final String that is in the widget class. This way works but violates widget immutability:


class LetterTextForm extends StatefulWidget {

  LetterTextForm({@required this.label, this.prefixIcon});
  final Widget prefixIcon;
  final String label;
  String text = '';

  String get textEntered => text;



  @override
  _LetterTextFormState createState() => _LetterTextFormState();
}

class _LetterTextFormState extends State<LetterTextForm> {
  var controller = TextEditingController();
  
  void textCallback() {
    widget.text = controller.text;
  }

  @override
  void initState() {
    super.initState();
  }

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


  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
        decoration: InputDecoration(
          prefixIcon: widget.prefixIcon,
          labelText: widget.label,
        )
    );
  }
}


Is there a way to do this that both 1) doesn't violate immutability and 2) doesn't dispose of the TextEditingController() immediately when the user hits Enter?

It works when I get rid of the dispose() method, but that seems like it could cause some performance/memory efficiency issues later. Maybe I don't know what I'm talking about there...

Perhaps I should try something different entirely and place the TextEditingControllers further up in the tree with the ancestors of the LetterTextForm widget, and not internally within the widget?

1 Answers

Create a ValueChanged<String> callback member and call it back when data is completed.

class LetterTextForm{
  ...
  final ValueChanged<String> onChanged;
  ...
}

class _LetterTextFormState extends ... {
  final _controller = TextEditingController();
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(...) {
    return TextField(
      controller: _controller,
      onSubmitted: (value) {
        if (widget.onChanged != null) {
          widget.onChanged(value); // returns entered value to caller
        }
      },
    );
  }
}

P.S. If to use TextFormField then controller is not necessary and you can use initialData for initialization.

Related