Flutter 对身份验证表单使用BLOC模式

Flutter 对身份验证表单使用BLOC模式,flutter,Flutter,我试图在包含登录和注册的基本身份验证表单上使用BLOC模式,其中登录和注册之间的唯一区别是注册有一个额外的确认密码字段,该字段也有助于确定是否应启用注册按钮 我有两个问题: 1.这是一个问题。当前,如果我输入通过login验证的某个登录名,然后切换到Signup表单,Signup按钮被启用,这是错误的,因为Confirm Password仍然为空。如何解决这个问题? 2.我觉得有比我所做的更好的方法来实现确认密码验证和注册按钮验证。我最初尝试为Confirm Password创建一个验证器,但它

我试图在包含登录和注册的基本身份验证表单上使用BLOC模式,其中登录和注册之间的唯一区别是注册有一个额外的
确认密码
字段,该字段也有助于确定是否应启用
注册
按钮

我有两个问题: 1.这是一个问题。当前,如果我输入通过
login
验证的某个登录名,然后切换到
Signup
表单,
Signup
按钮被启用,这是错误的,因为
Confirm Password
仍然为空。如何解决这个问题? 2.我觉得有比我所做的更好的方法来实现
确认密码
验证和
注册
按钮验证。我最初尝试为
Confirm Password
创建一个验证器,但它将同时使用Password和Confirm Password作为输入,但无法使其工作,因为
StreamTransformer
仅使用一个输入参数。做这件事最好的方法是什么

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:rxdart/rxdart.dart';


void main() => runApp(AuthProvider(child: MaterialApp(home: Auth())));

enum AuthMode { Signup, Login }

class Auth extends StatefulWidget {
  @override
  _AuthState createState() => _AuthState();
}

class _AuthState extends State<Auth> {
  AuthMode authMode = AuthMode.Login;
  bool get _isLoginMode => authMode == AuthMode.Login;
  TextEditingController confirmPasswordCtrl = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final bloc = AuthProvider.of(context);
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(20.0),
        child: Column(
          children: <Widget>[
            emailField(bloc),
            passwordField(bloc),
            confirmPasswordField(bloc),
            Container(
              margin: EdgeInsets.only(top: 40.0),
            ),
            FlatButton(
              child: Text('Switch to ${_isLoginMode ? 'Signup' : 'Login'}'),
              onPressed: swithAuthMode,
            ),
            loginOrSignupButton(bloc),
          ],
        ),
      ),
    );
  }

  void swithAuthMode() {
    setState(() {
      authMode = authMode == AuthMode.Login ? AuthMode.Signup : AuthMode.Login;
    });
  }

  Widget confirmPasswordField(AuthBloc bloc) {
    return _isLoginMode
        ? Container()
        : StreamBuilder(
            stream: bloc.passwordConfirmed,
            builder: (context, snapshot) {
              return TextField(
                obscureText: true,
                onChanged: bloc.changeConfirmPassword,
                keyboardType: TextInputType.text,
                decoration: InputDecoration(
                  labelText: 'Confirm Password',
                  errorText: snapshot.hasData && !snapshot.data ? 'password mismatch' : null,
                ),
              );
            },
          );
  }

  Widget emailField(AuthBloc bloc) {
    return StreamBuilder(
      stream: bloc.email,
      builder: (context, snapshot) {
        return TextField(
          keyboardType: TextInputType.emailAddress,
          onChanged: bloc.changeEmail,
          decoration: InputDecoration(
            hintText: 'your email',
            labelText: 'Email',
            errorText: snapshot.error,
          ),
        );
      },
    );
  }

  Widget loginOrSignupButton(AuthBloc bloc) {
    return StreamBuilder(
      stream: _isLoginMode ? bloc.submitValid : bloc.signupValid,
      builder: (context, snapshot) {
        print('hasData: ${snapshot.hasData}, data: ${snapshot.data}');
        return RaisedButton(
          onPressed: // The problem is, after entering some login details then switching from login to signup, the Signup button is enabled.
              !snapshot.hasData || !snapshot.data ? null : () => onSubmitPressed(bloc, context),
          color: Colors.blue,
          child: Text('${_isLoginMode ? 'Log in' : 'Sign up'}'),
        );
      },
    );
  }

  void onSubmitPressed(AuthBloc bloc, BuildContext context) async {
    var response = await bloc.submit(_isLoginMode);
    if (response.success) {
      Navigator.pushReplacementNamed(context, '/home');
    } else {
      showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
              title: Text('Error'),
              content: Text(response.message),
              actions: <Widget>[
                FlatButton(
                  child: Text('Ok'),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              ],
            );
          });
    }
  }

  Widget passwordField(AuthBloc bloc) {
    return StreamBuilder(
      stream: bloc.password,
      builder: (_, snapshot) {
        return TextField(
          obscureText: true,
          onChanged: bloc.changePassword,
          decoration: InputDecoration(
            labelText: 'Password',
            errorText: snapshot.error,
            hintText: 'at least 6 characters',
          ),
        );
      },
    );
  }
}

class AuthProvider extends InheritedWidget {
  final bloc;

  AuthProvider({Key key, Widget child}) :
    bloc = AuthBloc(), super(key:key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static AuthBloc of(BuildContext context) => (context.inheritFromWidgetOfExactType(AuthProvider) as AuthProvider).bloc;

}

 class Repository {
   // this will call whatever backend to authenticate users.
  Future<AuthResult> signupUser(String email, String password) => null;
  Future<AuthResult> loginUser(String email, String password) => null;
}


class AuthBloc extends Object with AuthValidator {
  final _emailController = BehaviorSubject<String>();
  final _passwordController = BehaviorSubject<String>();
  final _confirmPasswordController = BehaviorSubject<String>();
  final _signupController = PublishSubject<Map<String, dynamic>>();
  final Repository _repository = Repository();

  Stream<String> get email => _emailController.stream.transform(validateEmail);

  Stream<String> get password =>
      _passwordController.stream.transform(validatePassword);

  Stream<bool> get submitValid =>
      Observable.combineLatest2(email, password, (e, p) => true);

  // Is there a better way of doing passwordConfirmed and signupValid?
  Stream<bool> get passwordConfirmed =>
      Observable.combineLatest2(password, _confirmPasswordController.stream, (p, cp) => p == cp);

  Stream<bool> get signupValid =>
      Observable.combineLatest2(submitValid, passwordConfirmed, (s, p) => s && p);


  // sink
  Function(String) get changeEmail => _emailController.sink.add;
  Function(String) get changePassword => _passwordController.sink.add;
  Function(String) get changeConfirmPassword =>
      _confirmPasswordController.sink.add;

  Future<AuthResult> submit(bool isLogin) async {
    final validEmail = _emailController.value;
    final validPassword = _passwordController.value;
    if (!isLogin)
      return await _repository.signupUser(validEmail, validPassword);

    return await _repository.loginUser(validEmail, validPassword);
  }

  void dispose() {
    _emailController.close();
    _passwordController.close();
    _signupController.close();
    _confirmPasswordController.close();
  }
}

class AuthResult {
  bool success;
  String message;
  AuthResult(this.success, this.message);
}

// demo validator
class AuthValidator {
  final validateEmail = StreamTransformer<String, String>.fromHandlers(
    handleData: (email, sink) {
      if (email.contains('@')) sink.add(email);
      else sink.addError('Email is not valid');
    }
  );

  final validatePassword = StreamTransformer<String, String>.fromHandlers(
    handleData: (password, sink) {
      if (password.length >= 6) sink.add(password);
      else sink.addError('Password must be at least 6 characters');
    }
  );
}
导入“包装:颤振/材料.省道”;
导入“dart:async”;
导入“包:rxdart/rxdart.dart”;
void main();
枚举身份验证模式{注册,登录}
类Auth扩展StatefulWidget{
@凌驾
_AuthState createState()=>\u AuthState();
}
类AuthState扩展了状态{
AuthMode AuthMode=AuthMode.Login;
bool get_isLoginMode=>authMode==authMode.Login;
TextEditingController确认密码Ctrl=TextEditingController();
@凌驾
小部件构建(构建上下文){
final bloc=AuthProvider.of(上下文);
返回脚手架(
主体:容器(
裕度:所有边缘集(20.0),
子:列(
儿童:[
emailField(集团),
密码字段(集团),
confirmPasswordField(集团),
容器(
边距:仅限边缘集(顶部:40.0),
),
扁平按钮(
子项:Text('Switch to${{u isLoginMode?'Signup':'Login'}'),
按下:swithAuthMode,
),
罗吉诺西努布顿酒店(集团),
],
),
),
);
}
void swithAuthMode(){
设置状态(){
authMode=authMode==authMode.Login?authMode.Signup:authMode.Login;
});
}
小部件确认密码字段(AuthBloc bloc){
返回_isLoginMode
?容器()
:StreamBuilder(
流:bloc.passwordconfirm,
生成器:(上下文,快照){
返回文本字段(
蒙昧文字:对,
onChanged:bloc.changeConfirmPassword,
键盘类型:TextInputType.text,
装饰:输入装饰(
labelText:“确认密码”,
errorText:snapshot.hasData&!snapshot.data?“密码不匹配”:null,
),
);
},
);
}
小部件emailField(AuthBloc bloc){
返回流生成器(
流:bloc.email,
生成器:(上下文,快照){
返回文本字段(
键盘类型:TextInputType.emailAddress,
onChanged:bloc.changemail,
装饰:输入装饰(
hintText:“您的电子邮件”,
labelText:“电子邮件”,
errorText:snapshot.error,
),
);
},
);
}
小部件loginOrSignupButton(AuthBloc bloc){
返回流生成器(
流:_isLoginMode?bloc.submitValid:bloc.signupValid,
生成器:(上下文,快照){
打印('hasData:${snapshot.hasData},data:${snapshot.data}');
返回上升按钮(
onPressed://问题是,在输入一些登录详细信息,然后从登录切换到注册后,注册按钮被启用。
!snapshot.hasData | |!snapshot.data?null:()=>onSubmitPressed(bloc,context),
颜色:颜色,蓝色,
子项:文本(“${u isLoginMode?”登录“:“注册”}”),
);
},
);
}
void onSubmitPressed(AuthBloc bloc,BuildContext上下文)异步{
var response=wait bloc.submit(_isLoginMode);
if(response.success){
pushReplacementNamed(上下文“/home”);
}否则{
显示对话框(
上下文:上下文,
生成器:(上下文){
返回警报对话框(
标题:文本(“错误”),
内容:文本(响应消息),
行动:[
扁平按钮(
子项:文本('Ok'),
已按下:(){
Navigator.of(context.pop();
},
),
],
);
});
}
}
小部件密码字段(AuthBloc bloc){
返回流生成器(
流:bloc.password,
构建器:(\uux,快照){
返回文本字段(
蒙昧文字:对,
onChanged:bloc.changePassword,
装饰:输入装饰(
labelText:“密码”,
errorText:snapshot.error,
hintText:'至少6个字符',
),
);
},
);
}
}
类AuthProvider扩展了InheritedWidget{
最后集团;
AuthProvider({Key,Widget child}):
bloc=AuthBloc(),super(key:key,child:child);
@凌驾
bool updateShouldNotify(InheritedWidget oldWidget)=>true;
静态AuthBloc of(BuildContext context)=>(context.inheritFromWidgetOfExactType(AuthProvider)作为AuthProvider);
}
类存储库{
//这将调用任何后端来验证用户。
未来注册用户(字符串电子邮件、字符串密码)=>null;
未来登录用户(字符串电子邮件、字符串密码)=>null;
}
类AuthBloc使用AuthValidator扩展对象{
final _emailController=BehaviorSubject();
final _passwordController=BehaviorSubject();
最终_confirmPasswordController=BehaviorSubject();
最终_signupController=PublishSubject();
最终存储库
Stream<String> get passwordConfirmed => _confirmPasswordController.stream
    .transform(validatePassword).doOnData((String confirmPassword){
        if(0 != _passwordController.value.compareTo(confirmPassword)){
            _confirmPasswordController.addError("Passwords do not match");
        }
});