Я пытаюсь использовать шаблон BLOC в базовой форме аутентификации, которая включает в себя как регистрацию, так и регистрацию, где единственная разница между регистрацией и регистрацией состоит в том, что регистрация имеет дополнительное поле Confirm Password
, которое также влияет на то, должна ли кнопка Signup
быть включен.
У меня два вопроса: 1. Это проблема.В настоящее время, если я ввожу какую-либо учетную запись, прошедшую проверку Login
, затем переключаюсь на форму Signup
, кнопка Signup
активируется, что неправильно, поскольку Confirm Password
все еще пусто.Как это исправить?2. Я чувствую, что есть лучший способ, чем то, что я сделал, для проверки Confirm Password
и Signup
.Сначала я пытался создать валидатор для 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');
}
);
}