由于官方文档没有中文版,所以自己翻译
一、 bloc
Events
Events是Bloc的输入。通常是为了响应用户交互(例如按钮按下)或生命周期事件(例如页面加载)而添加它们。
- 在设计应用程序时,我们需要退后一步,定义用户将如何与之交互。在我们的计数器应用程序的上下文中,我们将有两个按钮来递增和递减我们的计数器。
- 当用户点击其中一个按钮时,需要发生一些事情来通知我们的应用程序的“大脑”,以便它可以响应用户的输入。这是Events起作用的地方。
- 我们需要能够将递增和递减通知我们应用程序的“大脑”,因此我们需要定义这些事件。
enum CounterEvent { increment, decrement }
- 本例中,我们可以使用一个
enum
来表示事件,但在更复杂的情况下,则可能需要使用一个class
(特别是需要向Bloc传递信息时)。 - 至此,我们已经定义了我们的第一个event!注意,到目前为止我们还没有使用过Bloc,也没有发生任何神奇的事情。这只是普通的Dart代码。
States
States是Bloc的输出,代表应用程序状态的一部分。UI组件可以接收States的通知,并根据当前状态重绘其自身的某些部分。
到目前为止,我们已经定义了我们的应用将响应的两个Events:CounterEvent.increment
和CounterEvent.decrement
。
现在需要定义如何表示我们应用的State
由于我们正在构建一个计数器,因此我们的state非常简单:它只是一个整数,代表了计数器的当前值。
稍后我们将看到状态的更复杂的示例,但是本例中,原始类型非常适合作为state表示。
Transitions
从一个state变成另外一种state叫做Transition。
一个Transition包含:
- 当前state,
- 触发event,
- 将要变成的state
当用户与我们的计数器应用程序交互时,他们将触发Increment和Decrement事件,这些事件将更新计数器的状态。所有这些状态更改都可以描述为一系列Transitions。
例如,如果用户打开我们的应用并点击了增量按钮,我们将看到以下内容Transition。
{
"currentState": 0,
"event": "CounterEvent.increment",
"nextState": 1
}
由于记录了每个state变化,因此我们能够非常轻松地在对我们的应用程序进行检测的同时,跟踪所有用户交互和状态变化。另外,这使诸如时间旅行调试之类的事情成为可能
Streams
查阅官方文档来了解
Streams
Dart Documentation
- Stream是一个异步数据序列
Bloc建立在RxDart之上;但是,它抽象了所有RxDart特定的实现细节。
使用Bloc,必须熟练掌握Streams用法及其原理。
- 若你对Streams不熟悉,可以回忆一下家里的自来水管道(管道-Stream,水-异步数据)
我们可以通过写一个async*
方法,在Dart中创建一个Stream
Stream<int> countStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
通过把一个方法标记为async*
,我们可以使用yield
关键字并返回一个Stream
数据。在上面的示例中,我们将返回一个最大
整数参数的整型Stream
。
每次我们在async*
函数中触发yield
我们都会通过Stream
推送该数据。
我们可以通过几种方式使用上面的Stream
。如果我们想写一个函数来返回一个整数流的和,它可以是这样的:
Future<int> sumStream(Stream<int> stream) async {
int sum = 0;
await for (int value in stream) {
sum += value;
}
return sum;
}
通过将上述函数标记为async
,我们可以使用await
关键字并返回一个整数的Future
。在本例中,我们等待流中的每个值,并返回流中所有整数的和。
我们可以这样组合:
void main() async {
/// Initialize a stream of integers 0-9
Stream<int> stream = countStream(10);
/// Compute the sum of the stream of integers
int sum = await sumStream(stream);
/// Print the sum
print(sum); // 45
}
Blocs
- Bloc(Business Logic Component 业务逻辑组件)是将传入事件流转换为传出状态流的组件.(这是本章的重点)
- 每个Bloc必须继承自Bloc基类(该基类源自core block package)
import 'package:bloc/bloc.dart';
class CounterBloc extends Bloc<CounterEvent, int> {
}
在上面的代码片段中,我们将CounterBloc声明为将CounterEvents转换为int的Bloc。
- 每个Bloc必须定义一个初始状态,该状态是接收任何Events之前的状态。
本例中,我们希望计数器从0开始
@override
int get initialState => 0;
- 每个Bloc必须实现一个名为
mapEventToState
的函数。该函数将传入的event
作为参数,并必须返回表示层使用的新状态流。我们可以在任何时候使用state属性访问当前的Bloc状态。
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break;
}
}
- 完整的CounterBloc
import 'package:bloc/bloc.dart';
enum CounterEvent { increment, decrement }
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break;
}
}
}
- 注意:Bloc将忽略重复的状态。如果一个Bloc生成
State nextState
,其中State == nextState
,则不会发生转换,也不会对Stream<State>
进行更改。
这时你可能会问:如何用Event触发Bloc发出通知?
- 每个Bloc都有一个
add
方法,这个方法接收一个event
并触发mapEventToState
.可以从表示层或者Bloc内部调用Add
,并通知Bloc一个新的event
我们可以创建一个从0到3的简单应用程序
void main() {
CounterBloc bloc = CounterBloc();
for (int i = 0; i < 3; i++) {
bloc.add(CounterEvent.increment);
}
}
-
默认情况下,事件总是按照它们被添加的顺序处理,任何新添加的事件都会被加入队列。一旦mapEventToState完成执行,事件就被认为是完全处理过的。
上面代码片段中的Transitions将是
{
"currentState": 0,
"event": "CounterEvent.increment",
"nextState": 1
}
{
"currentState": 1,
"event": "CounterEvent.increment",
"nextState": 2
}
{
"currentState": 2,
"event": "CounterEvent.increment",
"nextState": 3
}
不幸的是,在当前状态下,我们将无法看到任何这些转换,除非我们重写onTransition
。
-
onTransition
是一个可重写的方法,用来处理每个本地Bloc的Transition转化 -
onTransition
仅在Bloc的状态变化前调用 - 提示:
onTransition
是个添加特定Bloc的日志/分析的好地方。
@override
void onTransition(Transition<CounterEvent, int> transition) {
print(transition);
}
重写onTransition
方法,我们可以方便的在Transition
发生时自定义转化助理。
我们可以在bloc级别处理Transitions
转化,也能处理Exceptions
异常
-
onError
是一个可重写的方法,用来处理每个本地Bloc的Exception异常。默认情况下,所以异常都会被忽略,不会影响Bloc的功能 - 注意:如果状态流接收到一个没有
stacktrace
的错误,则stacktrace
参数可能为null
- 提示:
onError
是个添加特定Bloc的错误处理的好地方
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
}
通过重写onError
我们可以方便的在Exception抛出时自定义异常处理
BlocDelegate
使用Bloc的一个额外好处是我们可以在一个地方访问所有Transitions
。在本例我们只有一个Bloc,但是在实际开发中一般会有许多Bloc来管理应用程序状态的不同部分。
如果我们想要做一些事情来响应所有的Transitions
转换,我们可以简单地创建我们自己的BlocDelegate
。
class SimpleBlocDelegate extends BlocDelegate {
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
}
- 注意:仅需要继承BlocDelegate类且重写
onTransition
方法
为了让Bloc使用SimpleBlocDelegate
,我们需要调整main()
函数
void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
CounterBloc bloc = CounterBloc();
for (int i = 0; i < 3; i++) {
bloc.add(CounterEvent.increment);
}
}
如果我们希望能够对添加的所有Events
进行响应,我们还可以在SimpleBlocDelegate
中重写onEvent
方法。
class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
}
如果我们希望能够对一个Bloc中抛出的所有Expections
异常进行响应,我们还可以在SimpleBlocDelegate
中重写onError
方法。
class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
print('$error, $stacktrace');
}
}
- 注意:BlocSupervisor是一个单例对象,它监视所有的Blocs并将职责委托给BlocDelegate。
二、flutter_bloc
(一)Bloc Widgets
BlocBuilder
BlocBuilder
是一个包含Bloc
与builder
方法的Flutter部件,它处理在构建部件的过程中的不同的状态。与StreamBuilder
类似,但是API更简洁,代码量更少。builder
方法是一个可能被多次调用,返回不同状态的widget的纯函数。
如果您想“做”任何事情来响应状态更改,如导航、显示对话框等,请参阅BlocListener
如果忽略了bloc参数,那么BlocBuilder
将使用BlocProvider
和当前的BuildContext
自动执行查找。
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)
只有当您希望提供一个范围限制的简单widget且不能通过父BlocProvider
和当前BuildContext
访问的bloc时,才指定该bloc。
BlocBuilder<BlocA, BlocAState>(
bloc: blocA, // provide the local bloc instance
builder: (context, state) {
// return widget here based on BlocA's state
}
)
若要在调用生成器builder方法时进行细粒度控制,可以为BlocBuilder
提供一个可选条件.该条件获取以前的bloc状态和当前的bloc状态,并返回一个布尔值。如果条件返回true,builder会根据state重新调用,widget将重新构建。如果条件返回false,则不会调用builder,widget也不会重新构建。
BlocBuilder<BlocA, BlocAState>(
condition: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
BlocProvider
BlocProvider
是通过 BlocProvider.of<T>(context
提供一个bloc给其children的Flutter widget。它用作依赖项注入(DI)widget,因此可以将一个bloc的单个实例提供给子树中的多个widgets。
通常BlocProvider
是用来创建新bloc,这些bloc将对子树的其余部分生效。下面代码中,由于BlocProvider
负责创建bloc,它将自动处理关闭bloc。
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
在某些情况下,可以使用BlocProvider
将现有bloc提供给widget树的新部分。现有bloc需要作用在一个新的route上时通常会这么用。下面代码中,BlocProvider
不会自动关闭bloc,因为它没有创建它。
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
无论是ChildA
还是ScreenA
我们都能通过BlocProvider.of<BlocA>(context)
检索BlocA
MultiBlocProvider
MultiBlocProvider
可以将多个BlocProvider
widgets合并成一个。
MultiBlocProvider
改进了可读性,可以避免嵌套多个Blocprovider
的写法。通过使用MultiBlocProvider
,我们可以从麻烦的旧写法:
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
child: BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
child: BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
child: ChildA(),
)
)
)
到简洁的新写法
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)
BlocListener
BlocListener
:获取一个BlocWidgetListener和一个可选的“Bloc”,并调用侦听器listener
来响应bloc中的状态更改的一个Flutter widget。它应该用于每个状态变化会触发一次的功能,比如导航栏、显示一个SnackBar
、显示一个Dialog
等等。
与BlocBuilder
中的builder
不同,每次状态更改(不包括initialState)只调用一次listener
,它是一个void
函数。
如果忽略了bloc参数,BlocListener
将使用BlocProvider
和当前BuildContext
自动执行查找
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
只有提供通过BlocProvider
和当前BuildContext
无法访问的bloc时,才指定bloc。
BlocListener<BlocA, BlocAState>(
bloc: blocA,
listener: (context, state) {
// do stuff here based on BlocA's state
}
)
若要在调用侦听器listener方法进行细粒度控制,可以为BlocListener
提供一个可选条件。该条件获取以前的bloc状态和当前的bloc状态,并返回一个布尔值。如果条件返回true,则使用state
调用侦听器。如果条件返回false,则不会使用state
调用侦听器。
BlocListener<BlocA, BlocAState>(
condition: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
}
child: Container(),
)
MultiBlocListener
MultiBlocListener
是用来合并多个BlocListener
成一个的widget。它能使可读性更好,避免了嵌套多个BlocListeners
的写法。也就是你会从下面这种繁琐的旧写法:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
child: BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
child: BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
child: ChildA(),
),
),
)
到简洁的新写法:
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)
RepositoryProvider
RepositoryProvider是通过RepositoryProvider.of<T>(context)
为其children提供一个repository存储库的Flutter widget。它是依赖项注入(DI)的widget,因此可以将repository存储库的单个实例提供给子树中的多个widgets。应该使用BlocProvider来提供blocs,而RepositoryProvider应该只用于repositories存储库。
RepositoryProvider(
builder: (context) => RepositoryA(),
child: ChildA(),
);
通过ChildA
我们可以依据RepositoryProvider.of<RepositoryA>(context)
检索Repository
MultiRepositoryProvider
MultiRepositoryProvider是合并多个RepositoryProvider成一个的widget。它可读性更好,避免了嵌套多个RepositoryProvider的写法。我们可以将原先繁琐的写法:
RepositoryProvider<RepositoryA>(
builder: (context) => RepositoryA(),
child: RepositoryProvider<RepositoryB>(
builder: (context) => RepositoryB(),
child: RepositoryProvider<RepositoryC>(
builder: (context) => RepositoryC(),
child: ChildA(),
)
)
)
替换为简洁的:
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
builder: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
builder: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
builder: (context) => RepositoryC(),
),
],
child: ChildA(),
)
(二)Usage
让我们看看如何使用BlocBuilder
将一个CounterPage
widget连接到一个CounterBloc
。
counter_bloc.dart
enum CounterEvent { increment, decrement }
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break;
}
}
}
counter_page.dart
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Center(
child: Text(
'$count',
style: TextStyle(fontSize: 24.0),
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counterBloc.add(CounterEvent.increment);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () {
counterBloc.add(CounterEvent.decrement);
},
),
),
],
),
);
}
}
至此,我们已经成功地将展示层与业务逻辑层分离。注意,CounterPage widget不知道用户点击按钮时发生了什么。widget只是告诉CounterBloc,用户已经按下了递增或递减按钮。