Flutter+Firebase注册功能报错:setState调用于空对象
先帮你拆解下遇到的两个核心问题,以及对应的修复方案:
1. setState调用在null对象上的错误
错误日志里的NoSuchMethodError: The method 'setState' was called on null,根源是你在_validateSubmitRegister里调用了_sheetController.setState(() { _loading = true; });,但_sheetController这个变量从来没有被初始化过,一直处于null状态。而且这里完全不需要用_sheetController的setState,直接用当前State类本身的setState方法就能更新状态。
2. TextFormField无法正确获取输入值
你的buildTextField方法里,onSaved: (input) => _saved = input这行代码逻辑错误:这里的_saved是方法的局部参数,赋值给它并不会更新类的成员变量_displayName、_email等。所以当你调用form.save()时,这些成员变量依然是null,后续的Firebase注册请求自然无法正常执行。
具体修复步骤
步骤1:修正setState调用错误
把_sheetController.setState(...)替换成当前State的setState,并且删除没用的_sheetController变量:
setState(() { _loading = true; });
步骤2:修复TextFormField的onSaved逻辑
修改buildTextField方法,去掉_saved参数,改成接收一个ValueChanged<String>类型的回调,让调用方直接传入成员变量的赋值逻辑:
Widget buildTextField( double width, String text, IconData icon, bool obscureText, String isEmpty, bool autoValidate, ValueChanged<String> onSaved, // 新增回调参数 ){ return Container( margin: EdgeInsets.only(bottom: 15), width: width * 0.9, child: TextFormField( validator: (input) => input.isEmpty ? isEmpty : null, onSaved: onSaved, // 直接使用传入的回调 autovalidate: autoValidate, obscureText: obscureText, decoration: InputDecoration( suffixIcon: Padding( padding: EdgeInsetsDirectional.only(end: 12.0), child: Icon(icon, color: Colors.red), ), hintText: text, hintStyle: TextStyle(color: Colors.redAccent, fontSize: 15.0), border: OutlineInputBorder( borderRadius: BorderRadius.circular(30.0), borderSide: BorderSide( width: 5.0, color: Colors.redAccent, ), ), ), ), ); }
然后在调用buildTextField时,传入对应的赋值逻辑:
buildTextField(width, "Digite seu nome", Icons.person, false, "Por favor, digite seu nome!", _autoValidate, (input) => _displayName = input), buildTextField(width, "Digite seu e-mail", Icons.email, false, "Por favor, digite seu e-mail!", _autoValidate, (input) => _email = input), buildTextField(width, "Digite sua senha", Icons.lock, true, "Por favor, digite sua senha!", _autoValidate, (input) => _password = input), buildTextField(width, "Confirme sua senha", Icons.lock, true, "Por favor, confirme sua senha!", _autoValidate, (input) => _passwordConfirm = input),
步骤3:添加密码校验和错误捕获
目前代码没有校验两次输入的密码是否一致,也没有处理Firebase注册的异常,这会导致按钮点击后无反馈。可以在_validateSubmitRegister里补充这些逻辑:
void _validateSubmitRegister() async{ final form = formKey.currentState; if (form.validate()) { form.save(); // 校验两次密码是否一致 if (_password != _passwordConfirm) { scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Senhas não coincidem!"))); return; } setState(() { _loading = true; }); try { AuthResult result = await FirebaseAuth.instance.createUserWithEmailAndPassword(email: _email, password: _password); FirebaseUser user = result.user; // 可选:更新用户的显示名称 UserUpdateInfo updateInfo = UserUpdateInfo(); updateInfo.displayName = _displayName; await user.updateProfile(updateInfo); setState(() { _loading = false; }); // 注册成功后跳转到首页 Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Home())); } catch (e) { setState(() { _loading = false; }); // 显示错误提示 scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Erro ao registrar: ${e.toString()}"))); } } else { setState(() { _autoValidate = true; // 开启自动校验,提示用户输入错误 }); } }
步骤4:添加加载状态(可选)
可以给按钮添加加载状态提示,避免用户重复点击:
RaisedButton.icon( onPressed: _loading ? null : _validateSubmitRegister, // 加载时禁用按钮 label: _loading ? CircularProgressIndicator(color: Colors.white) : Text( 'Registrar', style: TextStyle(fontSize: 15, color: Colors.white), ), color: Colors.red, icon: _loading ? SizedBox() : Icon(Icons.account_circle, color: Colors.white), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), ),
完整修正后的代码
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:cruke_app/main.dart'; import 'package:cruke_app/ui/login.dart'; class CrukeRegister extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Register(), ); } } class Register extends StatefulWidget { @override _RegisterState createState() => _RegisterState(); } class _RegisterState extends State<Register> { String _email, _password, _passwordConfirm, _displayName; final formKey = new GlobalKey<FormState>(); bool _autoValidate = false; final FirebaseAuth auth = FirebaseAuth.instance; bool _loading = false; final scaffoldKey = new GlobalKey<ScaffoldState>(); Widget buildTextField( double width, String text, IconData icon, bool obscureText, String isEmpty, bool autoValidate, ValueChanged<String> onSaved, ){ return Container( margin: EdgeInsets.only(bottom: 15), width: width * 0.9, child: TextFormField( validator: (input) => input.isEmpty ? isEmpty : null, onSaved: onSaved, autovalidate: autoValidate, obscureText: obscureText, decoration: InputDecoration( suffixIcon: Padding( padding: EdgeInsetsDirectional.only(end: 12.0), child: Icon(icon, color: Colors.red), ), hintText: text, hintStyle: TextStyle(color: Colors.redAccent, fontSize: 15.0), border: OutlineInputBorder( borderRadius: BorderRadius.circular(30.0), borderSide: BorderSide( width: 5.0, color: Colors.redAccent, ), ), ), ), ); } @override Widget build(BuildContext context) { double width = MediaQuery.of(context).size.width; double height = MediaQuery.of(context).size.height; return Scaffold( key: scaffoldKey, backgroundColor: Colors.white, body: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Center( child: Container( margin: EdgeInsets.only(top: height * 0.025), child: Image.asset("images/logo_vertical.png", width: width * 0.5), ), ), Form( key: formKey, autovalidate: _autoValidate, child: Column( children: <Widget>[ buildTextField(width, "Digite seu nome", Icons.person, false, "Por favor, digite seu nome!", _autoValidate, (input) => _displayName = input), buildTextField(width, "Digite seu e-mail", Icons.email, false, "Por favor, digite seu e-mail!", _autoValidate, (input) => _email = input), buildTextField(width, "Digite sua senha", Icons.lock, true, "Por favor, digite sua senha!", _autoValidate, (input) => _password = input), buildTextField(width, "Confirme sua senha", Icons.lock, true, "Por favor, confirme sua senha!", _autoValidate, (input) => _passwordConfirm = input), Container( height: height * 0.09, width: width * 0.9, padding: EdgeInsets.only(top: 10.0), child: RaisedButton.icon( onPressed: _loading ? null : _validateSubmitRegister, label: _loading ? CircularProgressIndicator(color: Colors.white) : Text( 'Registrar', style: TextStyle(fontSize: 15, color: Colors.white), ), color: Colors.red, icon: _loading ? SizedBox() : Icon(Icons.account_circle, color: Colors.white), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), ), ), ], ), ), Container( margin: EdgeInsets.only(top: 15), child: Material( child: InkWell( onTap: (){ Navigator.push(context, MaterialPageRoute(builder: (context) => CrukeLogin())); }, child: Text("Já tem uma conta? Faça Login!", style: TextStyle(fontSize: 15, color: Colors.red),), ), ), ), ], ), ), ); } void _validateSubmitRegister() async{ final form = formKey.currentState; if (form.validate()) { form.save(); if (_password != _passwordConfirm) { scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Senhas não coincidem!"))); return; } setState(() { _loading = true; }); try { AuthResult result = await FirebaseAuth.instance.createUserWithEmailAndPassword(email: _email, password: _password); FirebaseUser user = result.user; UserUpdateInfo updateInfo = UserUpdateInfo(); updateInfo.displayName = _displayName; await user.updateProfile(updateInfo); setState(() { _loading = false; }); Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Home())); } catch (e) { setState(() { _loading = false; }); scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Erro ao registrar: ${e.toString()}"))); } } else { setState(() { _autoValidate = true; }); } } }
内容的提问来源于stack exchange,提问作者Rafael Canuto

