Повторное использование виджетов с блоком

#flutter #dart #bloc #flutter-bloc

Вопрос:

Чего я хочу достичь?

Я использую библиотеку блоков Flutter для своего компонента аутентификации. Внутри страницы входа в систему у меня есть несколько текстовых полей, таких как имя пользователя/пароль, которыми я также собираюсь поделиться с другими страницами, такими как страница регистрации, забытый пароль, смена пароля и т.д. В основном, следуя СУХОМУ принципу.

Каждая страница (Регистрация, Логин, Забытый пароль и т.д.) имеет свой собственный компонент блока.

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

Чтобы понять смысл написанного выше, давайте взглянем на мой код.

войдите в систему.введите фрагмент кода из метода сборки со страницы входа в систему.

 Widget _loginForm(){
    return BlocListener<LoginBloc, LoginState>(
      listener: (context, state) {
        final status = state.formStatus;
        if (status is SubmissionFailed) {
         ...
        }
      },
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            // The reusable widget: Email input field.
            BlocBuilder<LoginBloc, LoginState>(
              builder:(context, state){
                return emailInputField(context, state);
              }
            ),
            ...

 

Теперь давайте взглянем на виджет Поле ввода электронной почты

 Widget emailInputField(BuildContext context, dynamic state) {
  return TextFormField(
    validator: (value) =>
        state.isValidEmail ? null : 'Please input a valid email',
    onChanged: (value) =>
        // HERE - I want to decouple this widget from "LoginBloc".
        context.read<LoginBloc>().add(LoginUsernameChanged(username: value)),
      labelText: 'Email',
    ...
  );
}
 

login_bloc.запустите Блок Входа в систему

 class LoginBloc extends Bloc<BaseEvent, LoginState>{
  LoginBloc() : super(LoginState());

  @override
  Stream<LoginState> mapEventToState(BaseEvent event) async*{
    yield* event.handleEvent(state);
  }

}
 

И класс событий входа в систему, для получения полной картины login_event.dart

 abstract class LoginEvent extends BaseEvent {
  AuthenticationService authService = GetIt.I.get<AuthenticationService>();
}

// Event 1
class LoginUsernameChanged extends LoginEvent {
  final String username;

  LoginUsernameChanged({this.username});

  @override
  Stream<LoginState> handleEvent(BaseState state) async* {
    // Dart style down-casting...
    LoginState loginState = state;
    yield loginState.copyWith(username: username);
  }
}
 

base_event.дротик

 abstract class BaseEvent {
  Stream<BaseState> handleEvent(BaseState state);
}
 

И опять же, есть ли способ отделить эту точку зрения от блока?
Я надеюсь, что мой вопрос имеет смысл после прочтения этого.

P. S

Чтобы сделать вещи еще проще, одна идея, о которой я думаю, состоит в том, чтобы сохранить один компонент блока, который будет обрабатывать группу связанных полей ввода (пароль/имя пользователя), а затем просто переходить в разные состояния, как я сделал на странице login.dart при вызове виджета emailInput.

Ответ №1:

Что бы я сделал, так это полностью отделил виджет от блока. Вместо того , чтобы брать блок state и использовать bloc.add , создайте зависимости, которые вы можете заполнить любым параметром.

В вашем примере вы бы имели:

 Widget emailInputField({
 required BuildContext context, 
 required bool isValidEmail, 
 required void Function(String?) onChange,
}) {
  return TextFormField(
    validator: (value) => isValidEmail ? null : 'Please input a valid email',
    onChanged: onChange,
    labelText: 'Email',
    ...
  );
}
 

Затем вы можете использовать emailInputField с любым блоком, который вы хотите. Или любой государственной библиотеки управления, если на то пошло.

Для вашего примера это дало бы:

 Widget _loginForm(){
    return BlocListener<LoginBloc, LoginState>(
      listener: (context, state) {
        final status = state.formStatus;
        if (status is SubmissionFailed) {
         ...
        }
      },
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            // The reusable widget: Email input field.
            BlocBuilder<LoginBloc, LoginState>(
              builder:(context, state){
                return emailInputField(
                  context: context,
                  isValidEmail: state.isValidEmail, 
                  onChanged: (value) => context.read<LoginBloc>().add(LoginUsernameChanged(username: value))
                );
              }
            ),
            ...
 

Комментарии:

1. Это отличная идея, но как передать новое значение функции onChange?

2. Смотрите мой обновленный ответ