n Flutter, TextEditingController
is typically used with StatefulWidget
because it needs to persist its state across widget rebuilds and be disposed after it is not needed.
Example
The best practice for managing a TextEditingController
is to use a StatefulWidget
so that the controller is properly disposed of when no longer needed.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // Properly dispose of the controller
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("StatefulWidget with TextEditingController")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(labelText: "Enter text"),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print(_controller.text);
},
child: const Text("Submit"),
),
],
),
),
);
}
}
TextEditingController
is properly disposed of when the widget is removed from the widget tree, preventing memory leaks.- It allows you to manage the state of the text input dynamically.
Alternative Approach: Using Provider
If you prefer a StatelessWidget
, you can use a state management solution like Provider
to manage the TextEditingController
.
Example Using Provider
:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TextEditingProvider(),
child: const MyApp(),
),
);
}
class TextEditingProvider extends ChangeNotifier {
final TextEditingController controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
@override
Widget build(BuildContext context) {
final textProvider = Provider.of<TextEditingProvider>(context);
return Scaffold(
appBar: AppBar(title: const Text("Provider with TextEditingController")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: textProvider.controller,
decoration: const InputDecoration(labelText: "Enter text"),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print(textProvider.controller.text);
},
child: const Text("Submit"),
),
],
),
),
);
}
}
Why Use Provider
?
TextEditingController
is managed globally and disposed of properly.- You can use
StatelessWidget
while still handling state dynamically. - It works well in larger applications where text controllers need to be shared between widgets.
Would you like an approach using Riverpod
or GetX
for even more flexibility?
Yes! You can use an InheritedWidget
to provide the TextEditingController
to its descendants. However, InheritedWidget
alone does not provide automatic state management, so you may need to combine it with StatefulWidget
to properly dispose of the TextEditingController
.
✅ Using InheritedWidget
for TextEditingController Management
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
// InheritedWidget to provide TextEditingController
class TextControllerProvider extends InheritedWidget {
final TextEditingController controller;
TextControllerProvider({
super.key,
required this.controller,
required Widget child,
}) : super(child: child);
static TextControllerProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<TextControllerProvider>();
}
@override
bool updateShouldNotify(TextControllerProvider oldWidget) {
return controller != oldWidget.controller;
}
}
// Root Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TextControllerWrapper(
child: const MyStatelessWidget(),
),
);
}
}
// Wrapper that creates and disposes of the controller
class TextControllerWrapper extends StatefulWidget {
final Widget child;
const TextControllerWrapper({super.key, required this.child});
@override
_TextControllerWrapperState createState() => _TextControllerWrapperState();
}
class _TextControllerWrapperState extends State<TextControllerWrapper> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextControllerProvider(
controller: _controller,
child: widget.child,
);
}
}
// Stateless Widget that consumes the TextEditingController
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
@override
Widget build(BuildContext context) {
final textController = TextControllerProvider.of(context)!.controller;
return Scaffold(
appBar: AppBar(title: const Text("InheritedWidget Example")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: "Enter text"),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print(textController.text);
},
child: const Text("Submit"),
),
],
),
),
);
}
}
🏆 Why Use InheritedWidget
?
- Allows
StatelessWidget
to use theTextEditingController
- Provides a reusable, global way to share the controller
- Disposes of the controller properly when no longer needed
❓ When to Use InheritedWidget
vs. Provider
| Feature | InheritedWidget
| Provider
|
|———————-|——————|———–|
Good for small apps? | ✅ Yes | ✅ Yes |
Good for large apps? | ❌ Not ideal | ✅ Yes (better scalability) |
Requires boilerplate? | ✅ Some | ❌ Less |
For small projects, InheritedWidget
is fine. However, for more complex apps, Provider
(or Riverpod
) is usually a better choice.
I’ve introduced this option because it is part of the Flutter SDK. You may use it without any external library. I’ve also written how to totally encapsulate the state https://programtom.com/dev/2024/10/17/minimal-flutter-code-to-extract-state-from-statefulwidget/ out of the stateful widget.
Use a Reusable Component
The text field dialog has
- readonly view of the text field with an edit feature
- editable field capsulated in a dialog where custom client side and server side validators may be plugged in.
- have an on changed event in your stateless widget that you could pass up the value to your preferred state management