Widget rebuild after TextField selection Flutter

Nadox56 picture Nadox56 · May 11, 2018 · Viewed 10k times · Source

I'm developping a Flutter App that needed to have a form. So when the user open the app, a Splash Screen appear before the form that have the following code :

import 'package:flutter/material.dart';
import '../model/User.dart';
import './FileManager.dart';
import './MyListPage.dart';

class UserLoader extends StatefulWidget {
  @override
  _UserLoaderState createState() => new _UserLoaderState();
}

class _UserLoaderState extends State<UserLoader> {
  final userFileName = "user_infos.txt";
  User _user;

  @override
  Widget build(BuildContext context) {
    print("build UserLoader");
    final _formKey = new GlobalKey<FormState>();
    final _firstNameController = new TextEditingController();
    final _lastNameController = new TextEditingController();
    final _emailController = new TextEditingController();
    final _phoneController = new TextEditingController();

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Informations"),
          actions: <Widget>[
            new IconButton(
                icon: const Icon(Icons.save),
                onPressed: () {
                  _user = _onFormValidate(
                      _formKey.currentState,
                      _firstNameController.text,
                      _lastNameController.text,
                      _emailController.text,
                      _phoneController.text);
                })
          ],
        ),
        body: new Center(
          child: new SingleChildScrollView(
              child: new Form(
                  key: _formKey,
                  child: new Column(children: <Widget>[
                    new ListTile(
                      leading: const Icon(Icons.person),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Prénom",
                        ),
                        keyboardType: TextInputType.text,
                        controller: _firstNameController,
                        validator: _validateName,
                      ),
                    ),
                    new ListTile(
                      leading: const Icon(Icons.person),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Nom",
                        ),
                        keyboardType: TextInputType.text,
                        controller: _lastNameController,
                        validator: _validateName,
                      ),
                    ),
Etc, etc ...

However when i tap the TextField, the keyboard appear and close immediately and all the component is rebuild. So it is impossible for me to complete the form..

Can someone have a solution please? Thanks in advance !

Answer

rbp picture rbp · May 29, 2018

You haven't given us the entire code for this, so I don't know what the context is.

One pitfall I myself have fallen into (and might be affecting you, as I gather from your description) is having a stateful widget nested inside another stateful widget.

For instance,

class Parent extends StatefulWidget {
  @override
  ParentState createState() => ParentState();

  (...)
}

class ParentState extends State<Parent> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Child(),
    );
  }

  (...)
}

class Child extends StatefulWidget {
  @override
  ChildState createState() => ChildState();

  (...)
}

class ChildState extends State<Child> {
  @override
  Widget build(BuildContext context) {
    return TextField(...);
  }

  (...)
}

The problem here is that a rebuild of Parent means that ParentState().build() is run, and a new Child instance is created, with a new ChildState object. Which resets everything.

Try not recreating ChildWidget, but instead saving it on ParentState, like so:

class Parent extends StatefulWidget {
  @override
  ParentState createState() => ParentState();

  (...)
}

class ParentState extends State<Parent> {
  Child _child;

  @override
  void initState() {
    _child = Child();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: child,
    );
  }

  (...)
}
// The rest remains the same

Edit: You just need to remember that, if your widget tree is a bit more complex, you may need to 1) pass a callback from the Parent to notify of state changes, and 2) not forget to also call setState() on the Child.