FLUTTER MOBX:构建函数返回空值。导致错误的相关小部件是 Observer
FLUTTER MOBX: A build function returned null. The relevant error-causing widget was Observer
我正在尝试使用 MOBX 在电子邮件和密码这两个字段中进行验证,并且我正在 [=29] 中计算两个函数的结果=]compuntig 调用了 formIsValid,但是 mobX 向我返回了这个错误:A build function returned null.
相关的导致错误的小部件是
观察者
我尝试以不同的方式做到这一点,但我做不到,而且我的电子邮件观察值 emailErrorLabel 和 passwordErrorLabel 不是影响 TextTormField 错误文本。
这是我的代码 ViewModel:
import 'package:covid_app/app/service/firebase/firebase_auth.dart';
import 'package:covid_app/app/service/firebase/firebase_auth_impl.dart';
import 'package:covid_app/app/ui/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
part 'login_viewmodel.g.dart';
class LoginViewModel = LoginViewModelBase with _$LoginViewModel;
abstract class LoginViewModelBase with Store {
@observable
String email = "";
@observable
String password = "";
@observable
bool error = false;
@observable
bool emailErrorLabel = false;
@observable
bool passwordErrorLabel = false;
final _auth = Auth();
@action
changeEmail(String newEmail) => email = newEmail;
@action
changePassword(String newPassword) => password = newPassword;
@action
setHasErrorOnEmail(bool value) => emailErrorLabel = value;
@action
setHasErrorOnPassword(bool value) => passwordErrorLabel = value;
bool emailIsValid() {
if (email.isNotEmpty && email.contains("@")) {
return true;
} else {
setHasErrorOnEmail(true);
return false;
}
}
bool passwordIsValid() {
if (password.isNotEmpty || password.length >= 8) {
return true;
} else {
setHasErrorOnPassword(true);
return false;
}
}
@computed
bool get formIsValid {
return emailIsValid() && passwordIsValid();
}
@action
Future<void> firebaseLogin(dynamic context) async {
try {
if (email.isNotEmpty && password.isNotEmpty) {
var userId;
await _auth.signIn(email, password).then((value) => userId = value);
userId.length > 0 ? homeNavigator(context) : error = true;
} else {
error = true;
}
} catch (Exception) {
error = true;
print("Login Error: $Exception");
}
}
void homeNavigator(context) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
}
我的登录页面:
import 'package:covid_app/app/ui/login/login_viewmodel.dart';
import 'package:covid_app/app/widgets/KeyboardHideable.dart';
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:covid_app/core/constants/string.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../../widgets/button_component.dart';
import 'widgets/text_form_field_component.dart';
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
var vm = LoginViewModel();
var value = zero;
var valueTextFields = sixtyEight;
TextEditingController controllerEmail = TextEditingController();
TextEditingController controllerPassword = TextEditingController();
void animatedTest() async {
Future.delayed(Duration(seconds: 0), () {
setState(() {
value = sixtyEight;
valueTextFields = zero;
});
});
}
@override
void initState() {
super.initState();
animatedTest();
}
@override
Widget build(BuildContext context) {
return KeyboardHideable(
child: Scaffold(
backgroundColor: darkPrimaryColor,
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(sixteen),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Card(
elevation: twelve,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(twentyFour)),
),
child: Padding(
padding: const EdgeInsets.all(thirtyTwo),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(),
Expanded(
flex: 8,
child: AnimatedContainer(
margin: EdgeInsets.only(bottom: value),
duration: Duration(seconds: 1),
child: Image.asset(
"assets/images/logo_covid_app.png")),
),
Expanded(
flex: 7,
child: AnimatedContainer(
margin:
EdgeInsets.only(top: valueTextFields),
duration: Duration(seconds: 1),
child: Column(
children: <Widget>[
Expanded(
flex: 2,
child: Observer(
builder: (_) =>
TextFormFieldComponent(
emailHintText,
false,
controllerEmail,
vm.changeEmail,
vm.emailErrorLabel,
emailErrorLabel),
),
),
Expanded(
flex: 2,
child: Observer(builder: (_) {
return TextFormFieldComponent(
passwordHintText,
true,
controllerPassword,
vm.changePassword,
vm.emailErrorLabel,
passwordErrorLabel);
}),
),
],
),
),
),
SizedBox(
height: twentyEight,
),
Expanded(
flex: 2,
child: Observer(
builder: (_) => ButtonComponent(
title: loginButtonLabel,
fillColor: rosePrimaryColor,
textColor: Colors.white,
loginFun: vm.formIsValid
? () => vm.firebaseLogin(context)
: null,
),
),
),
SizedBox(
height: twenty,
),
Expanded(
flex: 2,
child: ButtonComponent(
title: registerButtonLabel,
fillColor: darkPrimaryColor,
textColor: Colors.white,
loginFun: () {}),
),
Spacer()
],
),
),
),
),
],
),
),
),
),
),
),
),
);
}
}
我的 TextFormField 组件:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class TextFormFieldComponent extends StatefulWidget {
String hintText;
bool hideText;
TextEditingController genericControler;
bool genericValidation;
String errorMessage;
Function onChangedGeneric;
TextFormFieldComponent(this.hintText, this.hideText, this.genericControler,
this.onChangedGeneric, this.genericValidation, this.errorMessage);
@override
_TextFormFieldComponentState createState() => _TextFormFieldComponentState();
}
class _TextFormFieldComponentState extends State<TextFormFieldComponent> {
@override
Widget build(BuildContext context) {
return Theme(
data:
ThemeData(cursorColor: rosePrimaryColor, hintColor: darkPrimaryColor),
child: TextFormField(
onChanged: widget.onChangedGeneric,
controller: widget.genericControler,
obscureText: widget.hideText,
decoration: InputDecoration(
hintText: widget.hintText,
errorText: widget.genericValidation == true ? widget.errorMessage : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(width: two, color: darkPrimaryColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(
width: two,
color: rosePrimaryColor,
),
),
),
),
);
}
}
我的按钮组件:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class ButtonComponent extends StatefulWidget {
var title;
var fillColor;
var textColor;
Function loginFun;
ButtonComponent({Key key, this.title, this.fillColor, this.textColor, this.loginFun});
@override
_ButtonComponentState createState() => _ButtonComponentState();
}
class _ButtonComponentState extends State<ButtonComponent> {
@override
Widget build(BuildContext context) {
return Container(
width: hundredSeventyTwo,
height: fortyFour,
child: RaisedButton(
disabledColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
twentyFour,
),
),
onPressed: widget.loginFun,
color: widget.fillColor,
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
),
),
),
);
}
}
打印错误:https://i.stack.imgur.com/UmQd5.png / https://i.stack.imgur.com/K13rN.png
好吧,错误说明了一切,真的没什么可补充的。
在 formIsValid
计算中你调用了 2 个函数,这些函数反过来可能会修改 emailErrorLabel
或 passwordErrorLabel
并且,因为它们都 observable
并且在同一个渲染中使用,这是不允许的。
computed
应该是没有副作用的纯函数,它应该只是从其他 computed
、observable
或常量值中导出一些值。
我正在尝试使用 MOBX 在电子邮件和密码这两个字段中进行验证,并且我正在 [=29] 中计算两个函数的结果=]compuntig 调用了 formIsValid,但是 mobX 向我返回了这个错误:A build function returned null. 相关的导致错误的小部件是 观察者
我尝试以不同的方式做到这一点,但我做不到,而且我的电子邮件观察值 emailErrorLabel 和 passwordErrorLabel 不是影响 TextTormField 错误文本。
这是我的代码 ViewModel:
import 'package:covid_app/app/service/firebase/firebase_auth.dart';
import 'package:covid_app/app/service/firebase/firebase_auth_impl.dart';
import 'package:covid_app/app/ui/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
part 'login_viewmodel.g.dart';
class LoginViewModel = LoginViewModelBase with _$LoginViewModel;
abstract class LoginViewModelBase with Store {
@observable
String email = "";
@observable
String password = "";
@observable
bool error = false;
@observable
bool emailErrorLabel = false;
@observable
bool passwordErrorLabel = false;
final _auth = Auth();
@action
changeEmail(String newEmail) => email = newEmail;
@action
changePassword(String newPassword) => password = newPassword;
@action
setHasErrorOnEmail(bool value) => emailErrorLabel = value;
@action
setHasErrorOnPassword(bool value) => passwordErrorLabel = value;
bool emailIsValid() {
if (email.isNotEmpty && email.contains("@")) {
return true;
} else {
setHasErrorOnEmail(true);
return false;
}
}
bool passwordIsValid() {
if (password.isNotEmpty || password.length >= 8) {
return true;
} else {
setHasErrorOnPassword(true);
return false;
}
}
@computed
bool get formIsValid {
return emailIsValid() && passwordIsValid();
}
@action
Future<void> firebaseLogin(dynamic context) async {
try {
if (email.isNotEmpty && password.isNotEmpty) {
var userId;
await _auth.signIn(email, password).then((value) => userId = value);
userId.length > 0 ? homeNavigator(context) : error = true;
} else {
error = true;
}
} catch (Exception) {
error = true;
print("Login Error: $Exception");
}
}
void homeNavigator(context) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
}
我的登录页面:
import 'package:covid_app/app/ui/login/login_viewmodel.dart';
import 'package:covid_app/app/widgets/KeyboardHideable.dart';
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:covid_app/core/constants/string.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../../widgets/button_component.dart';
import 'widgets/text_form_field_component.dart';
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
var vm = LoginViewModel();
var value = zero;
var valueTextFields = sixtyEight;
TextEditingController controllerEmail = TextEditingController();
TextEditingController controllerPassword = TextEditingController();
void animatedTest() async {
Future.delayed(Duration(seconds: 0), () {
setState(() {
value = sixtyEight;
valueTextFields = zero;
});
});
}
@override
void initState() {
super.initState();
animatedTest();
}
@override
Widget build(BuildContext context) {
return KeyboardHideable(
child: Scaffold(
backgroundColor: darkPrimaryColor,
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(sixteen),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Card(
elevation: twelve,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(twentyFour)),
),
child: Padding(
padding: const EdgeInsets.all(thirtyTwo),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(),
Expanded(
flex: 8,
child: AnimatedContainer(
margin: EdgeInsets.only(bottom: value),
duration: Duration(seconds: 1),
child: Image.asset(
"assets/images/logo_covid_app.png")),
),
Expanded(
flex: 7,
child: AnimatedContainer(
margin:
EdgeInsets.only(top: valueTextFields),
duration: Duration(seconds: 1),
child: Column(
children: <Widget>[
Expanded(
flex: 2,
child: Observer(
builder: (_) =>
TextFormFieldComponent(
emailHintText,
false,
controllerEmail,
vm.changeEmail,
vm.emailErrorLabel,
emailErrorLabel),
),
),
Expanded(
flex: 2,
child: Observer(builder: (_) {
return TextFormFieldComponent(
passwordHintText,
true,
controllerPassword,
vm.changePassword,
vm.emailErrorLabel,
passwordErrorLabel);
}),
),
],
),
),
),
SizedBox(
height: twentyEight,
),
Expanded(
flex: 2,
child: Observer(
builder: (_) => ButtonComponent(
title: loginButtonLabel,
fillColor: rosePrimaryColor,
textColor: Colors.white,
loginFun: vm.formIsValid
? () => vm.firebaseLogin(context)
: null,
),
),
),
SizedBox(
height: twenty,
),
Expanded(
flex: 2,
child: ButtonComponent(
title: registerButtonLabel,
fillColor: darkPrimaryColor,
textColor: Colors.white,
loginFun: () {}),
),
Spacer()
],
),
),
),
),
],
),
),
),
),
),
),
),
);
}
}
我的 TextFormField 组件:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class TextFormFieldComponent extends StatefulWidget {
String hintText;
bool hideText;
TextEditingController genericControler;
bool genericValidation;
String errorMessage;
Function onChangedGeneric;
TextFormFieldComponent(this.hintText, this.hideText, this.genericControler,
this.onChangedGeneric, this.genericValidation, this.errorMessage);
@override
_TextFormFieldComponentState createState() => _TextFormFieldComponentState();
}
class _TextFormFieldComponentState extends State<TextFormFieldComponent> {
@override
Widget build(BuildContext context) {
return Theme(
data:
ThemeData(cursorColor: rosePrimaryColor, hintColor: darkPrimaryColor),
child: TextFormField(
onChanged: widget.onChangedGeneric,
controller: widget.genericControler,
obscureText: widget.hideText,
decoration: InputDecoration(
hintText: widget.hintText,
errorText: widget.genericValidation == true ? widget.errorMessage : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(width: two, color: darkPrimaryColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(
width: two,
color: rosePrimaryColor,
),
),
),
),
);
}
}
我的按钮组件:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class ButtonComponent extends StatefulWidget {
var title;
var fillColor;
var textColor;
Function loginFun;
ButtonComponent({Key key, this.title, this.fillColor, this.textColor, this.loginFun});
@override
_ButtonComponentState createState() => _ButtonComponentState();
}
class _ButtonComponentState extends State<ButtonComponent> {
@override
Widget build(BuildContext context) {
return Container(
width: hundredSeventyTwo,
height: fortyFour,
child: RaisedButton(
disabledColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
twentyFour,
),
),
onPressed: widget.loginFun,
color: widget.fillColor,
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
),
),
),
);
}
}
打印错误:https://i.stack.imgur.com/UmQd5.png / https://i.stack.imgur.com/K13rN.png
好吧,错误说明了一切,真的没什么可补充的。
在 formIsValid
计算中你调用了 2 个函数,这些函数反过来可能会修改 emailErrorLabel
或 passwordErrorLabel
并且,因为它们都 observable
并且在同一个渲染中使用,这是不允许的。
computed
应该是没有副作用的纯函数,它应该只是从其他 computed
、observable
或常量值中导出一些值。