Handle changes to a text field

In some cases, it’s useful to run a callback function every time the text in a text field changes. For example, you might want to build a search screen with autocomplete functionality where you want to update the results as the user types.

How do you run a callback function every time the text changes? With Flutter, you have two options:

  1. Supply an onChanged() callback to a TextField.
  2. Use a TextEditingController.

1. Supply an onChanged() callback to a TextField

The simplest approach is to supply an onChanged() callback to a TextField. Whenever the text changes, the callback is invoked. One downside to this approach is that it doesn’t work with TextFormField widgets.

In this example, print the current value of the text field to the console every time the text changes.

TextField(
  onChanged: (text) {
    print("First text field: $text");
  },
);

2. Use a TextEditingController

A more powerful, but more elaborate approach, is to supply a TextEditingController as the controller property of the TextField or a TextFormField.

To be notified when the text changes, listen to the controller using the addListener() method using the following steps:

  1. Create a TextEditingController.
  2. Connect the TextEditingController to a text field.
  3. Create a function to print the latest value.
  4. Listen to the controller for changes.

Create a TextEditingController

Create a TextEditingController:

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  @override
  _MyCustomFormState createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller. Later, use it to retrieve the
  // current value of the TextField.
  final myController = TextEditingController();

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the
    // widget tree.
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Fill this out in the next step.
  }
}

Connect the TextEditingController to a text field

Supply the TextEditingController to either a TextField or a TextFormField. Once you wire these two classes together, you can begin listening for changes to the text field.

TextField(
  controller: myController,
);

Create a function to print the latest value

You need a function to run every time the text changes. Create a method in the _MyCustomFormState class that prints out the current value of the text field.

_printLatestValue() {
  print("Second text field: ${myController.text}");
}

Listen to the controller for changes

Finally, listen to the TextEditingController and call the _printLatestValue() method when the text changes. Use the addListener() method for this purpose.

Begin listening for changes when the _MyCustomFormState class is initialized, and stop listening when the _MyCustomFormState is disposed.

class _MyCustomFormState extends State<MyCustomForm> {
  @override
  void initState() {
    super.initState();

    // Start listening to changes.
    myController.addListener(_printLatestValue);
  }
}

Complete example

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Retrieve Text Input',
      home: MyCustomForm(),
    );
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  @override
  _MyCustomFormState createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller and use it to retrieve the current value
  // of the TextField.
  final myController = TextEditingController();

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

    myController.addListener(_printLatestValue);
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the widget tree.
    // This also removes the _printLatestValue listener.
    myController.dispose();
    super.dispose();
  }

  _printLatestValue() {
    print("Second text field: ${myController.text}");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            TextField(
              onChanged: (text) {
                print("First text field: $text");
              },
            ),
            TextField(
              controller: myController,
            ),
          ],
        ),
      ),
    );
  }
}