Flutter Lesson 28 of 83 3 min read
TextField and TextEditingController in Flutter
Learn TextField in Flutter: read input with onChanged and controllers, decorate fields, set keyboard types, handle focus and passwords.
On this page
TextField lets the user type. There are two ways to get what they typed.
onChanged: react to every keystroke #
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: GreetingPage()));
class GreetingPage extends StatefulWidget {
const GreetingPage({super.key});
@override
State<GreetingPage> createState() => _GreetingPageState();
}
class _GreetingPageState extends State<GreetingPage> {
String _name = '';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Your name',
border: OutlineInputBorder(),
),
onChanged: (value) => setState(() => _name = value),
),
const SizedBox(height: 24),
Text(_name.isEmpty ? 'Hello!' : 'Hello, $_name!'),
],
),
),
);
}
}
TextEditingController: read or change the text any time #
A controller holds the field’s text. Use it when you need the value later, such as on a button press, or need to set or clear the text from code.
class _SearchBoxState extends State<SearchBox> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // always
super.dispose();
}
void _search() {
debugPrint('Searching for ${_controller.text}');
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Search products',
prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: _controller.clear,
),
),
textInputAction: TextInputAction.search,
onSubmitted: (_) => _search(),
);
}
}
Create the controller in the State class, never inside build, and dispose it.
InputDecoration #
| Property | Shows |
|---|---|
labelText | A label that floats above the field when focused |
hintText | Grey placeholder text |
helperText | A note below the field |
errorText | A red message. Also turns the border red |
prefixIcon, suffixIcon | Icons inside the field |
prefixText, suffixText | Fixed text, such as Rs. or kg |
border | OutlineInputBorder() or the default underline |
filled, fillColor | A background |
counterText | Character count |
Set the look once for the whole app with inputDecorationTheme in your theme.
The right keyboard #
const TextField(keyboardType: TextInputType.emailAddress)
const TextField(keyboardType: TextInputType.phone)
const TextField(keyboardType: TextInputType.number)
const TextField(keyboardType: TextInputType.multiline, maxLines: 4)
keyboardType changes the keys shown. It does not restrict what can be entered. To do that, add input formatters:
import 'package:flutter/services.dart';
TextField(
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
],
)
Passwords #
TextField(
obscureText: _hidden,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(_hidden ? Icons.visibility : Icons.visibility_off),
onPressed: () => setState(() => _hidden = !_hidden),
),
),
)
Other useful properties #
| Property | Effect |
|---|---|
maxLength | Limit and show a counter |
maxLines | Lines visible. null grows without limit |
textCapitalization | words for names, sentences for messages |
textInputAction | The action key: next, done, search, send |
autofocus | Focus as soon as the screen opens |
enabled, readOnly | Disable or lock |
autofillHints | Lets the system offer saved emails and passwords |
onSubmitted | Called when the action key is pressed |
Focus #
A FocusNode lets you move the cursor between fields or dismiss the keyboard.
final _passwordFocus = FocusNode();
TextField(
textInputAction: TextInputAction.next,
onSubmitted: (_) => _passwordFocus.requestFocus(),
)
TextField(focusNode: _passwordFocus)
// Hide the keyboard:
FocusScope.of(context).unfocus();
// Dispose the node in dispose().
With textInputAction: TextInputAction.next, Flutter already moves to the next field without a focus node in most layouts.
Do not put a controller’s text in setState on every key #
If you use a controller and only need the value on submit, you do not need onChanged or setState at all. To rebuild part of the UI as the text changes, listen to the controller:
ValueListenableBuilder<TextEditingValue>(
valueListenable: _controller,
builder: (context, value, child) => Text('${value.text.length} characters'),
)
Try it yourself #
Build a “new message” box: a multi-line field with a 140-character limit, a live count of remaining characters, and a Send button that is disabled while the field is empty and clears the field when pressed.