Флаттер Blo c конфликтующие состояния - PullRequest
0 голосов
/ 28 марта 2020

Я пытаюсь создать активность входа в систему, используя Blo c, с помощью учебных пособий, доступных по адресу https://bloclibrary.dev/. Я успешно объединил валидацию формы и поток входа в рабочее решение, но при добавлении кнопки для переключения видимости пароля дела пошли не так, как надо.

Я решил, что я буду придерживаться того же формата, что и проверки и вход в систему состояние имело (виджет onPressed запускает событие, blo c обрабатывает его и изменяет состояние для обновления представления), но поскольку состояния являются взаимоисключающими, переключение видимости пароля приводит к исчезновению другой информации (например, ошибок проверки или индикатора загрузки), потому что состояние, которое им требуется для отображения, больше не является активным.

Я предполагаю, что одним из способов избежать этого является наличие отдельного блока Blo c для обработки только переключения пароля, но я думаю, это предполагает вложение второго BlocBuilder, на мой взгляд, не говоря уже о реализации другого набора состояний Bloc + Events +, который звучит так, как будто это может затруднить понимание и навигацию кода по мере усложнения. Это как Blo c предназначен для использования, или есть более чистый подход, который лучше работает здесь, чтобы избежать этого?

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,
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}
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 }';
}

1 Ответ

1 голос
/ 28 марта 2020

Да, вы не должны иметь это. // class DisplayPassword extends LoginState {}

И да, если вы хотите go pure BLo C, это правильный путь к go imo. В этом случае, поскольку единственным состоянием, которое вы хотите сохранить, является одно значение bool, вы можете go с более простым подходом со структурой BLo C. Я имею в виду, что вам не нужно создавать полный набор, класс событий, класс состояний, класс blo c, а вместо этого просто класс blo c. Кроме того, вы можете разделить папку blo c на 2 вида.

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;
  }
}
...