(译)Flutter bloc包

原文链接

在使用Flutter一段时间之后,我决定创造一个包帮忙我经常使用的东西—BLoC模式做一些事情。

对于那些不熟悉BLoC模式的人来说,它是一种设计模式,它有助于将表示层与业务逻辑分开。你从这里能够了解更多。

虽然使用BLoC模式可能会因为设置以及对StreamsReactive Programming的理解而具有挑战性,但它的核心BLoC是非常简单的:

BLoC将事件流作为输入,并将它们转换为状态流作为输出。

我们现在可以在bloc包的帮助下使用这种强大的设计模式。

该软件包抽象了BLoC模式的响应式方面,允许开发人员专注于将事件转换为状态。

让我们从定义这些术语开始。

词汇表

事件(Events)是BLoC的输入。它们通常是UI事件,例如按钮按下。事件(Events)被发送(dispatched)并转换为状态(States)。

状态(State)是BLoC的产物。表示组件可以监听状态流并根据给定状态对其自身的部分进行重绘(有关详细信息,请参阅BlocBuilder)。

转换(Transitions)发生在mapEventToState被调用之后,当事件被发送之后,但在bloc状态被修改之前。一个转换(Transition)由当前状态(currentState),已分送的事件(event)和下个状态(nextState)组成。

现在我们了解事件和状态,我们可以看一下Bloc API了。

Bloc API

mapEventToState是一个类在扩展Bloc时必须实现的方法。该方法将传入事件作为参数。只要有一个事件被表示层发送(dispatched),就会调用mapEventToStatemapEventToState必须将该事件转换为新状态,并Stream的形式返回新状态,而这个新状态会被表示层使用。

dispatch是一个接收事件并触发mapEventToState的方法。可以从表示层或从Bloc内部调用dispatch(参见示例)并向Bloc通知新事件来了。

initialState是任何事件都没有被处理之前的状态(在调用mapEventToState之前)。initialState是一个可选的getter。如果未实现,则initialState将为null。

transform是一个方法,可以在调用mapEventToState之前重写Stream。这其中允许使用distinct()和debounce()之类的操作。

onTransition是一个方法,可以在发生转换时覆盖对其加以处理。转换(Transition)发生在新的事件(event)被发送以及mapEventToState被调用的时候。onTransition在bloc的状态被更新之前被调用。这是添加bloc特有的日志记录/分析的好地方。

让我们创建一个计数器bloc!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 currentState - 1;
break;
case CounterEvent.increment:
yield currentState + 1;
break;
}
}
}

为了创建一个计数器bloc,我们需要做的就是:

  • 定义我们的事件和状态
  • 扩展BloC
  • 覆盖initialStatemapEventToState

在这种情况中,我们的事件是CounterEvents,我们的状态是整数类型。

我们的CounterBlocCounterEvents转换为整数类型。

我们可以通过像这样调用dispatch方法来通知CounterBloc发出事件:

1
2
3
4
5
6
void main() {
final counterBloc = CounterBloc();

counterBloc.dispatch(CounterEvent.increment);
counterBloc.dispatch(CounterEvent.decrement);
}

为了观察状态变化(Transitions),我们可以覆盖onTransition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;

@override
void onTransition(Transition<CounterEvent, int> transition) {
print(transition);
}

@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield currentState - 1;
break;
case CounterEvent.increment:
yield currentState + 1;
break;
}
}

现在,每当我们调度CounterEvent时,我们的Bloc将以新的整数状态响应,我们将看到一个转换记录被打印到到控制台。

现在让我们使用Flutter构建一个UI,并使用flutter_bloc包将表示层连接到我们的CounterBloc

flutter_bloc包提供了两个widget,使得可以轻松地与Bloc进行交互:

BlocBuilder

BlocBuilder是一个Flutter widget,它需要一个Bloc和一个builder方法。 BlocBuilder处理了构建widget的工作以响应新状态。BlocBuilder与StreamBuilder非常相似,但它有一个更简单的API来减少所需的样板代码量。

BlocProvider

BlocProvider是一个Flutter widget,它通过BlocProvider.of(context)为其子节点提供一个集合。它被用作依赖注入(DI)widget,以便可以将BloC的单例提供给子树中的多个widget。

现在让我们构建我们的Counter App!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

class App extends StatefulWidget {
@override
State<StatefulWidget> createState() => _AppState();
}

class _AppState extends State<App> {
final CounterBloc _counterBloc = CounterBloc();

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider<CounterBloc>(
bloc: _counterBloc,
child: CounterPage(),
),
);
}

@override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
}

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<CounterEvent, int>(
bloc: _counterBloc,
builder: (BuildContext context, int 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.dispatch(CounterEvent.increment);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () {
_counterBloc.dispatch(CounterEvent.decrement);
},
),
),
],
),
);
}
}

我们的App widget是StatefulWidget,负责创建和处理CounterBloc。它使用我们上面提到的BlocProvider widget使CounterBloc可用于CounterPage widget。

我们的CounterPage widget是一个StatelessWidget,它使用BlocBuilder重建UI以响应CounterBloc的状态变化。

此时,我们已经成功地将我们的表示层与业务逻辑层分开。请注意,CounterPage widget 不知道用户点击按钮时会发生什么。widget只是告诉CounterBloc用户按下了递增或递减按钮。

就这么多。

有关更多示例和详细文档,请查看官方bloc文档

如果你喜欢这个bloc库,你可以通过⭐️仓库或者👏来支持我这个文章。

非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号