美文网首页Flutter记录自学flutter点点滴滴flutter_bloc
Flutter 学习之旅(四十八) Flutter 状态 f

Flutter 学习之旅(四十八) Flutter 状态 f

作者: Tsm_2020 | 来源:发表于2020-10-21 15:31 被阅读0次

    今天继续看flutter_bloc 官网的demo,并且利用BLoC<Event,State> 封装一个输入框的功能,
    先来说一下flutter_bloc 封装能实现什么,有什么好处,

    利用flutter_bloc 中的BLoC 可以将Widget 和 业务逻辑解耦,并且利用事件的改变驱动着状态的改变,从而当 BLoCBuilder 的buildWhen为空或者 buildWhen执行后为true 的时候,执行setState,重绘Widget

    上面的过程就中就包含使用flutter_bloc 中BLoC的几个重要组成部分,其中包含Event 事件 ,State 状态 , Widget, 还有BLoC

    下面就使用flutter_bloc 官网的demo 来引出今天我们要说的问题

    我们要实现的效果


    GIF 2020-10-21 13-53-17.gif

    就是两个输入框,每次输入的信息改变后,检查输入信息是否正确,如果都正确则将按钮置位可以点击的状态,不正确则显示错误的的信息

    既然是两个输入框,和一个按钮,那就是是三个状态,
    我们先来创建状态信息

    abstract class MyFormEvent extends Equatable {
      const MyFormEvent();
      @override
      List<Object> get props => [];
    }
    class EmailChanged extends MyFormEvent {
      const EmailChanged({@required this.email});
    
      final String email;
    
      @override
      List<Object> get props => [email];
    }
    class PasswordChanged extends MyFormEvent {
      const PasswordChanged({@required this.password});
      final String password;
      @override
      List<Object> get props => [password];
    }
    class FormSubmitted extends MyFormEvent {}
    

    这里为了方便调用验证信息,demo中使用formz 创建实体,来验证状态

    enum EmailValidationError { invalid }
    class Email extends FormzInput<String, EmailValidationError> {
      const Email.pure() : super.pure('');
      const Email.dirty([String value = '']) : super.dirty(value);
    
      static final _emailRegex = RegExp(
        r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
      );
    
      @override
      EmailValidationError validator(String value) {
        return _emailRegex.hasMatch(value) ? null : EmailValidationError.invalid;
      }
    }
    
    enum PasswordValidationError { invalid }
    class Password extends FormzInput<String, PasswordValidationError> {
      const Password.pure() : super.pure('');
      const Password.dirty([String value = '']) : super.dirty(value);
    
      static final _passwordRegex =
      RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
    
      @override
      PasswordValidationError validator(String value) {
        return _passwordRegex.hasMatch(value)
            ? null
            : PasswordValidationError.invalid;
      }
    }
    

    既然事件有了,我们接下来继续创建事件对应的状态,

    class  MyFormState extends Equatable {
      const MyFormState({
        this.email = const Email.pure(),
        this.password = const Password.pure(),
        this.status = FormzStatus.pure,
      });
    
      final Email email;
      final Password password;
      final FormzStatus status;
    
      MyFormState copyWith({
        Email email,
        Password password,
        FormzStatus status,
      }) {
        return MyFormState(
          email: email ?? this.email,
          password: password ?? this.password,
          status: status ?? this.status,
        );
      }
    
      @override
      List<Object> get props => [email, password, status];
    }
    

    我们再利用BLoC来将事件和状态关联起来

    class MyFormBloc extends Bloc<MyFormEvent, MyFormState> {
      MyFormBloc() : super(const MyFormState());
    
      @override
      void onTransition(Transition<MyFormEvent, MyFormState> transition) {
        print(transition);
        super.onTransition(transition);
      }
    
      @override
      Stream<Transition<MyFormEvent, MyFormState>> transformEvents(
          Stream<MyFormEvent> events,
          TransitionFunction<MyFormEvent, MyFormState> transitionFn,
          ) {
        final debounced = events
            .where((event) => event is! FormSubmitted)
            .debounceTime(const Duration(milliseconds: 300));
        return events
            .where((event) => event is FormSubmitted)
            .mergeWith([debounced]).switchMap(transitionFn);
      }
    
      @override
      Stream<MyFormState> mapEventToState(MyFormEvent event) async* {
        if (event is EmailChanged) {
          final email = Email.dirty(event.email);
          yield state.copyWith(
            email: email,
            status: Formz.validate([email, state.password]),
          );
        } else if (event is PasswordChanged) {
          final password = Password.dirty(event.password);
          yield state.copyWith(
            password: password,
            status: Formz.validate([state.email, password]),
          );
        } else if (event is FormSubmitted) {
          if (state.status.isValidated) {
            yield state.copyWith(status: FormzStatus.submissionInProgress);
            await Future<void>.delayed(const Duration(seconds: 1));
            yield state.copyWith(status: FormzStatus.submissionSuccess);
          }
        }
      }
    }
    

    关于async* yield 关键字可以看上一篇文章的描述

    接下来的使用方法就和我们在前面flutter_bloc 学习(一) 的使用方法差不多了,

    class TsmFormValidation extends StatelessWidget{
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Form Validation 学习'),
            centerTitle: true,
          ),
          body: BlocProvider(
            create: (_)=>MyFormBloc(),
            child: MyForm(),
          ),
        );
      }
      
    }
    
    class MyForm extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return BlocListener<MyFormBloc, MyFormState>(
          listener: (context, state) {
            if (state.status.isSubmissionSuccess) {
              Scaffold.of(context).hideCurrentSnackBar();
              showDialog<void>(
                context: context,
                builder: (_) => SuccessDialog(),
              );
            }
            if (state.status.isSubmissionInProgress) {
              Scaffold.of(context)
                ..hideCurrentSnackBar()
                ..showSnackBar(
                  const SnackBar(content: Text('Submitting...')),
                );
            }
          },
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: <Widget>[
                EmailInput(),
                PasswordInput(),
                SubmitButton(),
              ],
            ),
          ),
        );
      }
    }
    
    class EmailInput extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<MyFormBloc, MyFormState>(
          buildWhen: (previous, current) => previous.email != current.email,
          builder: (context, state) {
            return TextFormField(
              initialValue: state.email.value,
              decoration: InputDecoration(
                icon: const Icon(Icons.email),
                labelText: 'Email',
                errorText: state.email.invalid ? 'Invalid Email' : null,
              ),
              keyboardType: TextInputType.emailAddress,
              onChanged: (value) {
                context.bloc<MyFormBloc>().add(EmailChanged(email: value));
              },
            );
          },
        );
      }
    }
    
    class PasswordInput extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<MyFormBloc, MyFormState>(
          buildWhen: (previous, current) => previous.password != current.password,
          builder: (context, state) {
            return TextFormField(
              initialValue: state.password.value,
              decoration: InputDecoration(
                icon: const Icon(Icons.lock),
                labelText: 'Password',
                errorText: state.password.invalid ? 'Invalid Password' : null,
              ),
              obscureText: true,
              onChanged: (value) {
                context.bloc<MyFormBloc>().add(PasswordChanged(password: value));
              },
            );
          },
        );
      }
    }
    
    class SubmitButton extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<MyFormBloc, MyFormState>(
          buildWhen: (previous, current) => previous.status != current.status,
          builder: (context, state) {
            return RaisedButton(
              onPressed: state.status.isValidated
                  ? () => context.bloc<MyFormBloc>().add(FormSubmitted())
                  : null,
              child: const Text('Submit'),
            );
          },
        );
      }
    }
    
    class SuccessDialog extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Dialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8),
          ),
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Row(
                  mainAxisSize: MainAxisSize.max,
                  children: <Widget>[
                    const Icon(Icons.info),
                    const Flexible(
                      child: Padding(
                        padding: EdgeInsets.all(10),
                        child: Text(
                          'Form Submitted Successfully!',
                          softWrap: true,
                        ),
                      ),
                    ),
                  ],
                ),
                RaisedButton(
                  child: const Text('OK'),
                  onPressed: () => Navigator.of(context).pop(),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    demo 到这里就结束了,但是关于MyFormBloc 的实现我看了好久才明白其中的道理,
    我们就以demo 发送事件, 转换事件为状态,并下发状态的这个过程来分析一下

    发送事件是由context.bloc 的add 方法开始的,

    context.bloc<MyFormBloc>().add(EmailChanged(email: value));
    

    我们就从add 事件说起,首先我们先来看一下Bloc和他的父类关系,

    abstract class Bloc<Event, State> extends Cubit<State>{
      final _eventController = StreamController<Event>.broadcast();
    }
    
    abstract class Cubit<State> extends Stream<State> {
      StreamController<State> _controller;
    }
    

    这里我们看到Bloc继承了Cubit并维护了一个_eventController , Cubit维护了一个_controller ,
    那么在add的过程中实现了什么呢,

    abstract class Bloc<Event, State> extends Cubit<State>{
      @override
      void add(Event event) {
        if (_eventController.isClosed) return;
        try {
          onEvent(event);
          _eventController.add(event);
        } on dynamic catch (error, stackTrace) {
          onError(error, stackTrace);
        }
      }
    }
    

    可以看到Bloc中重写了add 方法,并将这个事件添加到_eventController中,我们再继续看add 后,执行了什么,

    abstract class Bloc<Event, State> extends Cubit<State>
        implements EventSink<Event> {
      Bloc(State initialState) : super(initialState) {
        _bindEventsToStates();
      }
      void _bindEventsToStates() {
        _transitionSubscription = transformTransitions(
          transformEvents(
            _eventController.stream,
            (event) => mapEventToState(event).map(
              (nextState) => Transition(
                currentState: state,
                event: event,
                nextState: nextState,
              ),
            ),
          ),
        ).listen(
          (transition) {
            if (transition.nextState == state && _emitted) return;
            try {
              onTransition(transition);
              emit(transition.nextState);
            } on dynamic catch (error, stackTrace) {
              onError(error, stackTrace);
            }
            _emitted = true;
          },
          onError: onError,
        );
      }
    }
    

    我们看到在bloc 的初始化方法中调用了_bindEventsToStates()这个方法,从这个方法我们可以看到这里将事件转化为状态的,这里我们预留一个疑问他是如何转化的,这个疑问是这篇文章的重点,我们暂时不去管,先将整个过程分析一下
    我们可以看到_bindEventsToStates() 的listen 方法调用了emit方法,而Bloc 这个类没有重写这个方法,所以调用emit 调用的是Cubit 的emit 方法,

    abstract class Cubit<State> extends Stream<State> {
      @protected
      @visibleForTesting
      void emit(State state) {
        _controller ??= StreamController<State>.broadcast();
        if (_controller.isClosed) return;
        if (state == _state && _emitted) return;
        onChange(Change<State>(currentState: this.state, nextState: state));
        _state = state;
        _controller.add(_state);
        _emitted = true;
      }
    }
    

    我们可以看到Bloc调用 super.emit 后,将这个状态添加到了_controller 中,发送出去,
    这里我发现Bloc 中并没有重写listen方法,所以如果调用Bloc的listen方法就是调用Cubit 的listen方法,也就是监听的Cubit 中_controller 的stream.

    看完了这个过程我们再来总结一下整个过程

    Bloc 在初始化的时候创建了一个_eventController,并调用_eventController.stream.listen 方法,在调用context.bloc.add 方法的时候调用转化的方法,并且将转化后的state通过super.emit 方法 add到cubit 中_controller中,Bloc 中并没有重写listen方法,所以如果调用Bloc的listen方法就是调用Cubit 的listen方法,也就是监听的Cubit 中_controller 的stream.整个过程就完成了,

    下面我们仔细来说一下_bindEventsToStates()这个方法

    /// 转化Stream  ,如果你需要在创建Stream后,做一些变换的话,这是一个好的方法
      Stream<Transition<Event, State>> transformTransitions(
        Stream<Transition<Event, State>> transitions,
      ) {
        return transitions;
      }
    ///  重新添加Stream 
      Stream<Transition<Event, State>> transformEvents(
        Stream<Event> events,
        TransitionFunction<Event, State> transitionFn,
      ) {
        return events.asyncExpand(transitionFn);
      }
    
      void _bindEventsToStates() {
        _transitionSubscription = transformTransitions(
          transformEvents(
            _eventController.stream,
            (event) => mapEventToState(event).map(
              (nextState) => Transition(
                currentState: state,
                event: event,
                nextState: nextState,
              ),
            ),
          ),
        ).listen(
         ....
        );
      }
    

    我们可以看到他就是调用了_eventController.stream.asyncExpand(Stream<E>? convert(T event))后并且监听他,
    那么asyncExpand方法中到底实现了什么呢,

      Stream<E> asyncExpand<E>(Stream<E>? convert(T event)) {
        _StreamControllerBase<E> controller;
        ////先创建controller
        if (isBroadcast) {
          controller = _SyncBroadcastStreamController<E>(null, null);
        } else {
          controller = _SyncStreamController<E>(null, null, null, null);
        }
      ///监听controller
        controller.onListen = () {
    ///获取原有subscription 
          StreamSubscription<T> subscription = this.listen(null,
              onError: controller._addError, // Avoid Zone error replacement.
              onDone: controller.close);
    ///在原有的subscription收到回调的时候调用
          subscription.onData((T event) {
            Stream<E>? newStream;
            try {
    ///创建新的stream 
              newStream = convert(event);
            } catch (e, s) {
              controller.addError(e, s);
              return;
            }
            if (newStream != null) {
              subscription.pause();
    /// 转化controller 没有发生错误,则关联新的controller和新的stream,并返回stream
              controller.addStream(newStream).whenComplete(subscription.resume);
            }
          });
          controller.onCancel = subscription.cancel;
          if (!isBroadcast) {
            controller
              ..onPause = subscription.pause
              ..onResume = subscription.resume;
          }
        };
        return controller.stream;
      }
    

    这里我们就分析完了,根据这个asyncExpand 方法我们可以看到,每次收到信息的信息的时候都会重新创建Stream,好处是我们可以通过transformTransitions来干预生成的结果原因他是一个Stream,但是这种方法新人的话肯定肯定是想不到的,那么我怎么改才更容易让人理解呢,
    看看我们修改后的代码

    Transition<Event, State> mapEventToState(Event event);
    
    
      void _bindEventsToStates() {
        _eventController.stream.listen((event) {
          try {
            Transition transition = mapEventToState(event).map(
                  (nextState) =>
                  Transition(
                    currentState: state,
                    event: event,
                    nextState: nextState,
                  ),
            );
            printString(transition.toString());
            if (transition.nextState == state && _emitted) return;
            onTransition(transition);
            emit(transition.nextState);
            _emitted = true;
          } catch (error, stackTrace) {
            onError(error, stackTrace);
          }
        });
      }
    

    这种方式更让人容易理解,但是对后续操作一点也不友好,

    我学习flutter的整个过程都记录在里面了
    https://www.jianshu.com/c/36554cb4c804

    最后附上demo 地址

    https://github.com/tsm19911014/tsm_flutter

    相关文章

      网友评论

        本文标题:Flutter 学习之旅(四十八) Flutter 状态 f

        本文链接:https://www.haomeiwen.com/subject/ptusmktx.html