I have an issue with updating text inside TextFormField
when using Provider as state management.
I reduced my problem to abstract one (I removed all the clutter code) and here how it works:
- there is a
someValue
inAppState
- the
someValue
can be edited viaForm->TextFormField
- the
someValue
is to be reflected as a title of the AppBar when typing (onChange
) - the
someValue
can be updated from external source (in the example it is a button that updates it) - when
someValue
is updated from external source, it MUST be updated in textForm->TextFormField
as well
The last one is causing me the problem. Consider following code:
AppState.dart
import 'package:flutter/foundation.dart';
class AppState extends ChangeNotifier
{
String someValue = '';
updateSomeValue(String newValue)
{
someValue = newValue;
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';
void main() {
runApp(ChangeNotifierProvider(create: (_) => AppState(), child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<AppState>(context);
// following line of code makes it possible for text to be changed by button
// and reflected in TextFormField
// but it causes nasty side effect, that when typing, cursor always goes to beginning of the line
_controller.text = provider.someValue;
return Scaffold(
appBar: AppBar(
title: Text(provider.someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
onChanged: (v) {
provider.updateSomeValue(v);
},
),
ElevatedButton(
onPressed: () {
provider.updateSomeValue('foo_bar');
},
child: Text('change text external source'))
])),
),
);
}
}
The problem:
When I added the line _controller.text = provider.someValue;
it fixed the issue of updating TextFormField when button is clicked, but it create new issue, that when typing in TextFormField, it is also triggered, cause carret of text field to move to the beginning of the text field.
How to make it work so text (value) of a TextFormField can be updated externally, without causing carret
issue when typing?
CodePudding user response:
An easy way of doing this by listening TextEditingController
, while the TextFormField
is the ruler here.
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_controller = TextEditingController()
..addListener(() {
Provider.of<AppState>(context, listen: false)
.updateSomeValue(_controller.text);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.watch<AppState>().someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _controller,
),
ElevatedButton(
onPressed: () {
_controller.text = 'foo_bar';
},
child: Text('change text external source'))
],
),
),
),
);
}
}
Also, you can check riverpod