【发布时间】:2019-08-03 14:09:47
【问题描述】:
我的应用程序中有一个带有 TextField 表单验证器的注册页面,并带有一个按钮。如果未满足业务规则,文本字段将显示表单验证错误消息,并且一旦满足所有条件,“下一步”按钮将可点击。这目前在我的应用程序中运行良好,但我发现一旦我离开页面并返回它,验证错误消息就会停止显示,并且按钮也会停止工作。在我的 IDE (android studio) 中查看控制台日志,我得到的唯一相关错误消息是
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: Bad state:
Stream is already closed
#0 _SinkTransformerStreamSubscription._add
(dart:async/stream_transformers.dart:66:7)
#1 _EventSinkWrapper.add
(dart:async/stream_transformers.dart:15:11)
我不确定这意味着什么,重新加载页面后,流是否会关闭并且不会重新打开?如果没有,有没有办法可以解决这个问题,或者我缺少什么? This is我正在经历的事情
流生成器代码:
Widget emailField(authBloc) {
return StreamBuilder(
stream: authBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.deepOrange
)
),
hintText: 'Enter Email',
labelText: 'Email Address',
errorText: snapshot.error
),
);
},
);
}
Widget passwordField( authBloc) {
return StreamBuilder(
stream: authBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Enter password',
labelText: 'Password',
errorText: snapshot.error,
),
);
},
);
}
Widget checkPasswordField( authBloc) {
return StreamBuilder(
stream: authBloc.validatePasswordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateValidatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Re-enter password',
labelText: 'Confirm Password',
errorText: snapshot.error,
),
);
},
);
}
Widget nextBtn(authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Next'),
color: Colors.deepOrange,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: snapshot.hasData
? () => Navigator.pushNamed(context, '/register')
: null,
);
}
);
}
流:
/// REGISTER VARIABLES
static final _emailController = BehaviorSubject<
String>(); //RxDart's implementation of StreamController. Broadcast stream by default
static final _passwordController = BehaviorSubject<String>();
static final _validatePasswordController = BehaviorSubject<
String>(); // Will check that the password entered the 2nd time is correct
/// REGISTER STREAM & METHODS
//Retrieve data from the stream
Stream<String> get emailStream => _emailController.stream
.transform(performEmailValidation); //Return the transformed stream
Stream<String> get passwordStream =>
_passwordController.stream.transform(performPasswordValidation);
Stream<String> get validatePasswordStream =>
_validatePasswordController.stream.transform(performIsPasswordSame);
//Merging email, password and validate password
Stream<bool> get submitValid => Observable.combineLatest3(
emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);
//Add data to the stream
Function(String) get updateEmail => _emailController.sink.add;
Function(String) get updatePassword => _passwordController.sink.add;
Function(String) get updateValidatePassword =>
_validatePasswordController.sink.add;
// performing user input validations
final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) async {
String emailValidationRule =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailValidationRule);
if (await doesNameAlreadyExist("email", _emailController.value) == true)
sink.addError("That email already exists");
else if (regExp.hasMatch(email)) {
sink.add(email);
} else {
sink.addError(StringConstant.emailErrorMessage);
}
});
final performPasswordValidation =
StreamTransformer<String, String>.fromHandlers(
handleData: (_passwordController, sink) {
if (_passwordController.length >= 6) {
sink.add(_passwordController);
} else {
sink.addError(StringConstant.passwordErrorMessage);
}
});
final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password != _passwordController.value)
sink.addError(StringConstant.invalidPasswordMessage);
else
sink.add(password);
});
整个屏幕代码:
class SignUp extends StatefulWidget {
@override
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
AuthBloc _authBloc;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_authBloc = AuthBlocProvider.of(context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
SizedBox(height: 80,),
Text("Register", style: Style.appTextStyle),
SizedBox(height: 100,),
emailField(_authBloc),
SizedBox(height: 30),
passwordField(_authBloc),
SizedBox(height: 30),
checkPasswordField(_authBloc),
SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
cancelBtn(),
nextBtn(_authBloc),
],
)
// checkPasswordField(authBloc),
],
),
)
),
);
}
Widget emailField(authBloc) {
return StreamBuilder(
stream: authBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.deepOrange
)
),
hintText: 'Enter Email',
labelText: 'Email Address',
errorText: snapshot.error
),
);
},
);
}
Widget passwordField( authBloc) {
return StreamBuilder(
stream: authBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Enter password',
labelText: 'Password',
errorText: snapshot.error,
),
);
},
);
}
Widget checkPasswordField( authBloc) {
return StreamBuilder(
stream: authBloc.validatePasswordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateValidatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Re-enter password',
labelText: 'Confirm Password',
errorText: snapshot.error,
),
);
},
);
}
Widget nextBtn(authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Next'),
color: Colors.deepOrange,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: snapshot.hasData
? () => Navigator.pushNamed(context, '/register')
: null,
);
}
);
}
Widget cancelBtn(){
return RaisedButton(
child: Text('Cancel'),
color: Colors.white30,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: () => Navigator.pop(context),
);
}
@override
void dispose() {
super.dispose();
_authBloc.dispose();
}
区号:
/// REGISTER VARIABLES
static final _emailController = BehaviorSubject<
String>(); //RxDart's implementation of StreamController. Broadcast stream by default
static final _passwordController = BehaviorSubject<String>();
static final _validatePasswordController = BehaviorSubject<
String>(); // Will check that the password entered the 2nd time is correct
/// REGISTER STREAM & METHODS
//Retrieve data from the stream
Stream<String> get emailStream => _emailController.stream
.transform(performEmailValidation); //Return the transformed stream
Stream<String> get passwordStream =>
_passwordController.stream.transform(performPasswordValidation);
Stream<String> get validatePasswordStream =>
_validatePasswordController.stream.transform(performIsPasswordSame);
//Merging email, password and validate password
Stream<bool> get submitValid => Observable.combineLatest3(
emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);
//Add data to the stream
Function(String) get updateEmail => _emailController.sink.add;
Function(String) get updatePassword => _passwordController.sink.add;
Function(String) get updateValidatePassword =>
_validatePasswordController.sink.add;
// performing user input validations
final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) async {
String emailValidationRule =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailValidationRule);
if (await doesNameAlreadyExist("email", _emailController.value) == true)
sink.addError("That email already exists");
else if (regExp.hasMatch(email)) {
sink.add(email);
} else {
sink.addError(StringConstant.emailErrorMessage);
}
});
final performPasswordValidation =
StreamTransformer<String, String>.fromHandlers(
handleData: (_passwordController, sink) {
if (_passwordController.length >= 6) {
sink.add(_passwordController);
} else {
sink.addError(StringConstant.passwordErrorMessage);
}
});
final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password != _passwordController.value)
sink.addError(StringConstant.invalidPasswordMessage);
else
sink.add(password);
});
dispose() {
_emailController.close();
_passwordController.close();
_validatePasswordController.close();
}
【问题讨论】:
-
我需要了解您在何处以及如何创建和处置 BLoC 实例。如果可能,请分享整个屏幕代码和 BLoC。但正如消息错误所说“Stream is already closed”意味着您可能正在关闭 BLoC 流,当您走下一条路线时,当您返回“FORM SCREEN”时,您正在使用相同的BLoC 实例,但所有流都已关闭。你是用 InheritedWidget 来获取 Bloc 吗?
-
我用初始化和关闭 bloc 实例的代码更新了我的问题。一旦用户离开并返回页面,我如何使用另一个 BLoC 实例?是的,我正在使用 inheritWidget 来获取 Bloc
-
您应该使用与您已经使用的相同的 BLoC 实例。
标签: flutter flutter-layout stream-builder