Использовать шаблон BLOC для формы аутентификации - PullRequest
0 голосов
/ 28 сентября 2018

Я пытаюсь использовать шаблон 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');
    }
  );
}

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

В вашем случае, лучший способ сделать passwordConfirmed будет:

Stream<String> get passwordConfirmed => _confirmPasswordController.stream
    .transform(validatePassword).doOnData((String confirmPassword){
        if(0 != _passwordController.value.compareTo(confirmPassword)){
            _confirmPasswordController.addError("Passwords do not match");
        }
});

Как предложено boeledi здесь .

0 голосов
/ 01 октября 2018

После попытки воспроизвести поведение, я мог подтвердить, что поток signupValid имеет значение true, если submitValid имеет значение true, похоже, что вычисление для signupValid никогда не выполняется.

Один из способов обойти это - очистить текстовые поля и добавить пустую строку в потоки при изменении режима входа в систему с входа в систему до регистрации и наоборот.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...