今天继续看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 地址
网友评论