首页 > 解决方案 > setState 不更新 TextFormField 小部件的 initialValue

问题描述

我对 Flutter 完全陌生。我试图制作一个带有记住凭据的选项的登录表单。

由于SharedPreferences是异步的,它使这个任务有点棘手:我创建了一个异步方法 - 在构造函数中启动 - 以异步获取SharedPreferences实例,读取一些值,然后调用setState()写入新状态1

但是当setState()被调用并写入新状态时,UI 不会更新(即文本字段保持空白)。

build()方法类似于:

@override
Widget build(BuildContext context) {
  developer.log("build called: rememberCredentials=$rememberCredentials, username=$username, password=$password");
  return (... TextFormField(
    initialValue: username,
    onChanged: (String value) {username = value;}
  ) ...);
}

我添加了几个developer.log()调用来查看何时build()调用,以及是否实际读取了保存的首选项(它们确实读取了):

[log] build called: rememberCredentials=false, username=, password=
[log] read prefs: rememberCredentials=true, username=john, password=1234
[log] build called: rememberCredentials=true, username=john, password=1234
[log] build called: rememberCredentials=true, username=john, password=1234

这是相关代码:

class LoginForm extends StatefulWidget {
  const LoginForm({Key? key}) : super(key: key);

  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  String username = "";
  String password = "";
  bool rememberCredentials = false;

  _LoginFormState() {
    readSavedCredentials();
  }

  void readSavedCredentials() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      rememberCredentials = prefs.getBool("rememberCredentials") ?? false;
      username = prefs.getString("username") ?? "";
      password = prefs.getString("password") ?? "";
      developer.log("read prefs: rememberCredentials=$rememberCredentials, username=$username, password=$password");
    });
  }

  void login() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setBool("rememberCredentials", rememberCredentials);
    await prefs.setString("username", rememberCredentials ? username : "");
    await prefs.setString("password", rememberCredentials ? password : "");
    final resp = await http.get(
      Uri.parse('https://127.0.0.1:56873/?action=login&user=$username&pass=${md5hex(password)}')
    );
    if(resp.statusCode == 200) {
      Map o = jsonDecode(resp.body);
      if(o["status"] == "AUTH_OK") {
        developer.log(jsonEncode(o));
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => FilesView(o["base_url"], o["files"])),
        );
      } else {
        // TODO: report error
      }
    } else {
      // TODO: report error
    }
  }

  @override
  Widget build(BuildContext context) {
    developer.log("build called: rememberCredentials=$rememberCredentials, username=$username, password=$password");
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: Text(title())),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Padding(
              padding: EdgeInsets.all(15),
              child: TextFormField(
                  initialValue: username,
                  onChanged: (String value) {
                    username = value;
                  },
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Username',
                  )
              )
          ),
          Padding(
              padding: EdgeInsets.all(15),
              child: TextFormField(
                  initialValue: password,
                  onChanged: (String value) {
                    password = value;
                  },
                  obscureText: true,
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Password',
                  )
              )
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(0, 10, 0, 30),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Checkbox(
                    value: rememberCredentials,
                    onChanged: (bool? value) {
                      setState(() {
                        rememberCredentials = value!;
                      });
                    }),
                Text('Remember credentials'),
              ],
            ),
          ),
          Container(
            width: 220,
            decoration: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(20),
            ),
            child: TextButton(
              onPressed: () {
                login();
              },
              child: Padding(
                padding: EdgeInsets.all(10),
                child: Text(
                  'Login',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
        ]
      )
    );
  }
}

由于我对 Flutter 完全陌生,因此我可能犯了几个错误。非常欢迎任何关于设计的建议:)


1:另一种选择是使用FutureBuilder,但它更麻烦,并且会延迟 UI 创建...我更喜欢尽快显示 UI,然后更改字段值。

标签: flutterdart

解决方案


您可以使用文本编辑控制器,而不是使用字符串用户名来保存您的值并使用 onChanged 来更改它

String username = '';

变成

TextEditingController usernameController = TextEditingController();

然后在你的 TextFormFields

TextFormField(
  initialValue: username,
  onChanged: (String value) {
    username = value;
  },
  ...
)

变成

TextFormField(
  controller: usernameController,
  ...
)

最后,在您的 readSavedCredentials() 方法中,您可以使用文本编辑控制器设置文本(用户名)

void readSavedCredentials() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  setState(() {
    ...
    usernameController.text = prefs.getString("username") ?? "";
    ...
  });
}

推荐阅读