Flutter 颤振集团冲突国家
我正试图在上提供的教程的帮助下,使用Bloc构建一个登录活动。我已经成功地将表单验证和登录流程结合到一个工作解决方案中,但是当添加一个按钮来切换密码可见性时,事情发生了混乱 我想我会遵循验证和登录状态的相同格式(小部件的onPressed触发事件,bloc处理它并更改状态以更新视图),但由于状态是互斥的,切换密码可见性会导致其他信息(如验证错误或加载指示器)消失,因为它们显示所需的状态不再是活动状态 我认为避免这种情况的一种方法是使用一个单独的Bloc来处理密码切换,但我认为这涉及到在我的视图中嵌套第二个BlocBuilder,更不用说实现另一组Bloc+事件+状态,这听起来可能会使代码更难理解/导航,因为事情变得更复杂。这就是Bloc的使用方式,还是有一种更干净的方法可以更好地避免这种情况Flutter 颤振集团冲突国家,flutter,bloc,flutter-bloc,flutter-state,Flutter,Bloc,Flutter Bloc,Flutter State,我正试图在上提供的教程的帮助下,使用Bloc构建一个登录活动。我已经成功地将表单验证和登录流程结合到一个工作解决方案中,但是当添加一个按钮来切换密码可见性时,事情发生了混乱 我想我会遵循验证和登录状态的相同格式(小部件的onPressed触发事件,bloc处理它并更改状态以更新视图),但由于状态是互斥的,切换密码可见性会导致其他信息(如验证错误或加载指示器)消失,因为它们显示所需的状态不再是活动状态 我认为避免这种情况的一种方法是使用一个单独的Bloc来处理密码切换,但我认为这涉及到在我的视图中
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
_onLoginButtonPressed() {
BlocProvider.of<LoginBloc>(context).add(
LoginButtonPressed(
username: _usernameController.text,
password: _passwordController.text,
),
);
}
_onShowPasswordButtonPressed() {
BlocProvider.of<LoginBloc>(context).add(
LoginShowPasswordButtonPressed(),
);
}
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginFailure) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('${state.error}'),
backgroundColor: Colors.red,
),
);
}
},
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Form(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Username', prefixIcon: Icon(Icons.person)),
controller: _usernameController,
autovalidate: true,
validator: (_) {
return state is LoginValidationError ? state.usernameError : null;
},
),
TextFormField(
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
state is! DisplayPassword ? Icons.visibility : Icons.visibility_off,
color: ColorUtils.primaryColor,
),
onPressed: () {
_onShowPasswordButtonPressed();
},
),
),
controller: _passwordController,
obscureText: state is! DisplayPassword ? true : false,
autovalidate: true,
validator: (_) {
return state is LoginValidationError ? state.passwordError : null;
},
),
Container(height: 30),
ButtonTheme(
minWidth: double.infinity,
height: 50,
child: RaisedButton(
color: ColorUtils.primaryColor,
textColor: Colors.white,
onPressed: state is! LoginLoading ? _onLoginButtonPressed : null,
child: Text('LOGIN'),
),
),
Container(
child: state is LoginLoading
? CircularProgressIndicator()
: null,
),
],
),
),
);
},
),
);
}
}
classloginform扩展StatefulWidget{
@凌驾
State createState()=>\u LoginFormState();
}
类_LoginFormState扩展状态{
final _usernameController=TextEditingController();
final _passwordController=TextEditingController();
@凌驾
小部件构建(构建上下文){
_onLoginButtonPressed(){
BlocProvider.of(上下文)。添加(
登录按钮按下(
用户名:_usernameController.text,
密码:_passwordController.text,
),
);
}
_onShowPasswordButtonPressed(){
BlocProvider.of(上下文)。添加(
LoginShowPasswordButtonPressed(),
);
}
返回BlocListener(
侦听器:(上下文、状态){
如果(状态为登录失败){
Scaffold.of(上下文).showSnackBar(
小吃条(
内容:文本(“${state.error}”),
背景颜色:Colors.red,
),
);
}
},
孩子:BlocBuilder(
生成器:(上下文、状态){
报税表(
孩子:填充(
填充:常数边集全部(32.0),
子:列(
mainAxisAlignment:mainAxisAlignment.center,
儿童:[
TextFormField(
装饰:输入装饰(标签文本:“用户名”,前缀:图标(Icons.person)),
控制器:\ u usernameController,
自动验证:true,
验证器:(){
返回状态为LoginValidationError?state.UserName错误:null;
},
),
TextFormField(
装饰:输入装饰(
labelText:“密码”,
前缀:图标(图标锁定轮廓),
后缀:图标按钮(
图标:图标(
状态为!DisplayPassword?Icons.visibility:Icons.visibility\u off,
颜色:ColorUtils.primaryColor,
),
已按下:(){
_onShowPasswordButtonPressed();
},
),
),
控制器:_passwordController,
蒙蔽文本:状态为!显示密码?真:假,
自动验证:true,
验证器:(){
返回状态为LoginValidationError?state.passwordError:null;
},
),
货柜(高度:30),
钮扣(
minWidth:double.infinity,
身高:50,
孩子:升起按钮(
颜色:ColorUtils.primaryColor,
textColor:Colors.white,
onPressed:状态为!LoginLoading?\u onLoginButtonPressed:空,
子项:文本('LOGIN'),
),
),
容器(
子:状态为LoginLoading
?循环压缩机指示器()
:null,
),
],
),
),
);
},
),
);
}
}
classloginbloc扩展了Bloc{
最终用户存储库用户存储库;
最终认证Bloc AuthenticationBloc;
bool isShowingPassword=false;
罗金布洛克({
@需要此.userRepository,
@需要此.authenticationBloc,
}):assert(userRepository!=null),
断言(authenticationBloc!=null);
LoginState get initialState=>LoginInitial();
@凌驾
流mapEventToState(LoginEvent事件)异步*{
if(事件为LoginShowPasswordButtonPressed){
isShowingPassword=!isShowingPassword;
产生isShowingPassword?DisplayPassword():LoginInitial();
}
如果(事件为LoginButtonPressed){
如果(!_isUsernameValid(event.username)| |!_isPasswordValid(event.password)){
收益率登录验证错误(
usernameError:_isUsernameValid(event.username)?null:“(测试)验证失败”,
passwordError:_isPasswordValid(event.password)?null:“(测试)验证失败”,
);//TODO更新此字段,以便在多个条件下验证字段(字段是必需的、最小字符大小等),并向用户显示相应的字段
}
否则{
收益率LoginLoading();
最终响应=等待userRepository.authenticate(
用户名:event.username,
密码:event.password,
);
如果(response.ok!=null){
authenticationBloc.add(LoggedIn(用户:response.ok));
}
否则{
屈服登录失败(错误:response.err
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final UserRepository userRepository;
final AuthenticationBloc authenticationBloc;
bool isShowingPassword = false;
LoginBloc({
@required this.userRepository,
@required this.authenticationBloc,
}) : assert(userRepository != null),
assert(authenticationBloc != null);
LoginState get initialState => LoginInitial();
@override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
if (event is LoginShowPasswordButtonPressed) {
isShowingPassword = !isShowingPassword;
yield isShowingPassword ? DisplayPassword() : LoginInitial();
}
if (event is LoginButtonPressed) {
if (!_isUsernameValid(event.username) || !_isPasswordValid(event.password)) {
yield LoginValidationError(
usernameError: _isUsernameValid(event.username) ? null : "(test) validation failed",
passwordError: _isPasswordValid(event.password) ? null : "(test) validation failed",
); //TODO update this so fields are validated for multiple conditions (field is required, minimum char size, etc) and the appropriate one is shown to user
}
else {
yield LoginLoading();
final response = await userRepository.authenticate(
username: event.username,
password: event.password,
);
if (response.ok != null) {
authenticationBloc.add(LoggedIn(user: response.ok));
}
else {
yield LoginFailure(error: response.error.message);
}
}
}
}
bool _isUsernameValid(String username) {
return username.length >= 4;
}
bool _isPasswordValid(String password) {
return password.length >= 4;
}
}
abstract class LoginEvent extends Equatable {
const LoginEvent();
@override
List<Object> get props => [];
}
class LoginButtonPressed extends LoginEvent {
final String username;
final String password;
const LoginButtonPressed({
@required this.username,
@required this.password,
});
@override
List<Object> get props => [username, password];
@override
String toString() =>
'LoginButtonPressed { username: $username, password: $password }';
}
class LoginShowPasswordButtonPressed extends LoginEvent {}
abstract class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginValidationError extends LoginState {
final String usernameError;
final String passwordError;
const LoginValidationError({@required this.usernameError, @required this.passwordError});
@override
List<Object> get props => [usernameError, passwordError];
}
class DisplayPassword extends LoginState {}
class LoginFailure extends LoginState {
final String error;
const LoginFailure({@required this.error});
@override
List<Object> get props => [error];
@override
String toString() => 'LoginFailure { error: $error }';
}
bloc
- full
- login_bloc.dart
- login_event.dart
- login_state.dart
- single
- password_visibility_bloc.dart
class PasswordVisibilityBloc extends Bloc<bool, bool> {
@override
bool get initialState => false;
@override
Stream<bool> mapEventToState(
bool event,
) async* {
yield !event;
}
}