fluttertextfieldonbackpressedtexteditingcontrollerconstrainedbox

Problem with TextEditingController on back pressed


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.


Solution

  • 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.