I have the following StatefulWidget
in my Flutter application:
class EditProfilePage extends StatefulWidget {
const EditProfilePage({super.key});
@override
State<EditProfilePage> createState() => _EditProfilePageState();
}
class _EditProfilePageState extends State<EditProfilePage> {
ProfileClient client = ProfileClient();
TextEditingController usernameController = TextEditingController();
Future<ProfileResponse>? futureProfileResponse;
@override
void initState() {
super.initState();
futureProfileResponse = fetch();
}
Future<ProfileResponse> fetch() async {
String key = await Utils.getKey();
return client.get(key);
}
@override
void dispose() {
usernameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Edit profile'),
),
body: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height),
child: buildPage())));
}
FutureBuilder<ProfileResponse> buildPage() {
return FutureBuilder<ProfileResponse>(
future: futureProfileResponse,
builder: (context, snapshot) {
if (snapshot.hasData) {
ProfileResponse profileResponse = snapshot.data!;
if (profileResponse.code == 200) {
usernameController.text = profileResponse.profile.username;
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
TextField(
controller: usernameController,
decoration: const InputDecoration(
labelText: 'Username',
),
),
],
),
);
}
}
return const Center(child: CircularProgressIndicator());
});
}
}
I dont' understand the reason why, writing inside the username TextField
and then pressing the back button, the content of the TextField
comes back to the previous value.
This is a demo to make better understand my problem: Demo GIF
To be precise, this problem started to appear as soon as I wrapped the buildPage()
method with a ConstrainedBox
widget. I did so because in my original page I had more TextField
widgets and I needed them to "move up" when opening the keyboard. For the sake of simplicity, I removed parts of the code that were not related to this specific problem.
When the user opens/closes the keyboard, the available screen size will change, which makes the widget rebuilds. The entire FutureBuilder
will be rebuilt and the builder
will execute the line where you assign the text to the controller. You can verify by adding a print statement right before it:
print('Widget rebuild at ${DateTime.now()}');
usernameController.text = profileResponse.profile.username;
The reason you use FutureBuilder
is to render TextField
only after the future completes and render CircularProgressIndicator
otherwise. Setting the value for the controller should be separated from this widget building mechanism. You can set the text
value for the controller in the fetch
method:
Future<ProfileResponse> fetch() async {
String key = await Utils.getKey();
final profileResponse = client.get(key);
usernameController.text = profileResponse.profile.username;
return profileResponse;
}
In the FutureBuilder
, remove this line:
usernameController.text = profileResponse.profile.username;
This way, the usernameController.text
assignment line won't be called when the widget rebuilds.