Reputation: 175
I wrote logic with edit mode which allows user to make changes in input field, but when edit mode button is clicked again then input need back to value before editing. And there is a problem with that, because everything works fine but console is showing me this error:
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for TextEditingController:
setState() or markNeedsBuild() called during build.
This Form widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: Form-[LabeledGlobalKey<FormState>#bcaba]
state: FormState#65267
The widget which was currently being built when the offending call was made was: ProfileInput
dirty
state: _ProfileInputState#32ea5
I know what this error means, but I can't find a place responsible for this. Could someone explain it to me?
class Profile extends StatefulWidget {
@override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
User _user = User(
username: "name",
);
String? _tmpUsername;
bool _editMode = false;
void _createTemporaryData() {
_tmpUsername = _user.username;
}
void _restoreData() {
_user.username = _tmpUsername!;
}
void _changeMode() {
if (_editMode)
_restoreData();
else
_createTemporaryData();
setState(() {
_editMode = !_editMode;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () => _changeMode(), child: Text("change mode")),
Form(
key: _formKey,
child: ProfileInput(
editMode: _editMode,
user: _user,
onChangeName: (value) {
_user.username = value;
},
),
),
],
),
);
}
}
class ProfileInput extends StatefulWidget {
final bool editMode;
final User user;
final void Function(String value)? onChangeName;
ProfileInput({
required this.editMode,
required this.user,
required this.onChangeName,
});
@override
_ProfileInputState createState() => _ProfileInputState();
}
class _ProfileInputState extends State<ProfileInput> {
TextEditingController _nameController = TextEditingController();
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_nameController.text = widget.user.username;
return TextFormField(
onChanged: widget.onChangeName,
controller: _nameController,
enabled: widget.editMode,
);
}
}
Upvotes: 1
Views: 1041
Reputation: 5736
Put the following line in the initState or use addPostFrameCallback.
_nameController.text = widget.user.username; // goes into initState
@override
void initState() {
super.initState();
_nameController.text = widget.user.username;
}
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 1
SchedulerBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 2
// use either 1 or 2.
// rest of the code, return statement.
}
Calling text
setter on _nameController
would notify all the listener and it's called inside the build method during an ongoing build that causes setState() or markNeedsBuild() called during build.
From Documentation:
Setting this will notify all the listeners of this TextEditingController that they need to update (it calls notifyListeners). For this reason, this value should only be set between frames, e.g. in response to user actions, not during the build, layout, or paint phases.
Upvotes: 1