flutter
/

Flutter TextEditingController – Complete Guide with Examples

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter TextEditingController – Complete Guide with Examples

What is TextEditingController?

TextEditingController is a controller that manages the text being edited in a TextField or TextFormField. It allows you to programmatically read, modify, and listen to changes in the text input. Using a controller gives you fine‑grained control over the text field, such as setting initial values, clearing the text, or reacting to each keystroke.

Creating and Disposing a Controller

Always create the controller in initState and dispose it in dispose to prevent memory leaks. Never create a controller directly inside the build method because it would be recreated on every rebuild, leading to performance issues and unexpected behavior.

DARTRead-only
1
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final TextEditingController _controller = TextEditingController();

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

  @override
  Widget build(BuildContext context) {
    return TextField(controller: _controller);
  }
}

Reading the Text

Use the .text property to get the current content of the text field. This is useful when you need to retrieve the user's input (e.g., on button press).

DARTRead-only
1
String text = _controller.text;

Setting the Text Programmatically

To set or change the text, assign a new string to the .text property. The text field will update automatically. This is useful for pre‑filling fields, resetting them, or reflecting external changes.

DARTRead-only
1
// Set initial text after creation
_controller.text = 'Initial value';

// Clear the field
_controller.clear();

Listening to Text Changes

You can listen to changes in the text by adding a listener to the controller. The listener is called whenever the text changes (every keystroke). This is useful for implementing live validation, search‑as‑you‑type, or updating other parts of the UI.

DARTRead-only
1
@override
void initState() {
  super.initState();
  _controller.addListener(() {
    print('Current text: ${_controller.text}');
    // You can call setState here to rebuild other widgets
  });
}

Remember to remove the listener in dispose if you added it manually, but if you simply use addListener, the controller will manage the listener list; however, it's still good practice to remove it if you stop needing it. In the example above, it's fine to leave it as the controller will be disposed anyway.

Using with FocusNode

Often you combine TextEditingController with a FocusNode to control focus and text together. For example, you might want to clear the text when the field loses focus, or request focus after setting a value.

DARTRead-only
1
final FocusNode _focusNode = FocusNode();

@override
void dispose() {
  _focusNode.dispose();
  _controller.dispose();
  super.dispose();
}

void _clearAndFocus() {
  _controller.clear();
  _focusNode.requestFocus();
}

Selection and Cursor Control

TextEditingController also exposes the selection (cursor position and selected range). You can read and modify it using _controller.selection. For example, to move the cursor to the end after setting a new value:

DARTRead-only
1
void _setTextAndMoveCursor(String newText) {
  _controller.text = newText;
  _controller.selection = TextSelection.fromPosition(
    TextPosition(offset: _controller.text.length),
  );
}

Common Mistakes

    • Not disposing the controller: This leads to memory leaks, especially in stateful widgets that are frequently created/destroyed.
    • Creating the controller in build: The controller will be recreated on every rebuild, causing loss of text, listeners, and performance issues.
    • Setting .text without using setState: Changing .text does not automatically rebuild the widget; the text field updates internally, but if you need to reflect the change elsewhere, you may need to call setState.
    • Forgetting to add a listener in initState and remove it in dispose: If you add a listener manually, make sure to remove it in dispose using _controller.removeListener.

Best Practices

    • Always create the controller in initState and dispose in dispose.
    • Use _controller.clear() to reset the field.
    • For real‑time validation, add a listener and call setState to update error messages.
    • When setting text programmatically, consider moving the cursor to the end for better UX.
    • Combine with FocusNode to manage focus and text together.

Key Takeaways

    • TextEditingController gives you programmatic control over TextField and TextFormField.
    • Create in initState, dispose in dispose.
    • Use .text to read or write the content.
    • Add a listener with addListener to react to changes.
    • Combine with FocusNode for focus management.
    • Always dispose to avoid memory leaks.

Try it yourself

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Controller Demo',
      home: Scaffold(
        appBar: AppBar(title: Text('TextEditingController')),
        body: Center(child: ControllerDemo()),
      ),
    );
  }
}

class ControllerDemo extends StatefulWidget {
  @override
  _ControllerDemoState createState() => _ControllerDemoState();
}

class _ControllerDemoState extends State<ControllerDemo> {
  final TextEditingController _controller = TextEditingController();
  String _displayText = '';

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      setState(() {
        _displayText = _controller.text;
      });
    });
  }

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

  void _clear() {
    _controller.clear();
  }

  void _setDefault() {
    _controller.text = 'Default text';
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              labelText: 'Enter text',
              border: OutlineInputBorder(),
            ),
          ),
          SizedBox(height: 20),
          Text('You typed: $_displayText'),
          SizedBox(height: 20),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: _clear,
                child: Text('Clear'),
              ),
              SizedBox(width: 16),
              ElevatedButton(
                onPressed: _setDefault,
                child: Text('Set Default'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

Where should you create a TextEditingController?

A
In the build method
B
In initState
C
In the constructor
D
Anywhere, it doesn't matter
Q2
of 4

How do you read the current text from a TextEditingController?

A
_controller.getValue()
B
_controller.text
C
_controller.value
D
_controller.string
Q3
of 4

Why must you call `dispose` on a TextEditingController?

A
To clear the text
B
To remove listeners and prevent memory leaks
C
To focus another field
D
It's optional
Q4
of 4

Which method clears the text in the controller?

A
_controller.empty()
B
_controller.clear()
C
_controller.reset()
D
_controller.text = ''

Frequently Asked Questions

Can I use the same TextEditingController for multiple TextFields?

Technically yes, but they would share the same text, which is rarely intended. Usually you want one controller per text field.

How do I set the cursor position?

Use _controller.selection = TextSelection.fromPosition(TextPosition(offset: position));. To set it at the end: _controller.selection = TextSelection.fromPosition(TextPosition(offset: _controller.text.length));

Why does my text field not update when I change `_controller.text`?

The text field itself will update automatically. If you are displaying the text elsewhere (e.g., in a Text widget), you need to call setState or use a ValueListenableBuilder to rebuild that part of the UI.

How do I get notified when the user stops typing (debounce)?

You can use a Timer inside the listener. For example: Timer? _debounce; _controller.addListener(() { if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(Duration(milliseconds: 500), () { // action }); });

What's the difference between `TextEditingController` and `TextFormField`'s `onChanged`?

TextEditingController gives you more control (programmatic access, selection, listeners). onChanged is simpler for just reacting to changes, but you cannot read the text outside the callback or set it programmatically without a controller.

Previous

flutter getx

Next

flutter form validation

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.