在剛開始學習Flutter時如果讀到有關狀態管理的文章大部分都會是與「BLoC」相關的內容,雖然真的是有點複雜,但感覺還是得嘗試看看「BLoC」到底有什麼優點可以是Flutter中最多人推崇的架構(但也許現在的風向是GetX比較受歡迎)
所謂 BLoC 就是 Business Logic Components的縮寫,BLoC其實是一種 design pattern,而不只是套件。他的目標是在分離「畫面」以及「商業邏輯」,其實就跟我們之前在講狀態管理時的目標一樣。
BLoC的核心概念是將所有「事件」都視作stream:
我們透過送出「事件」然後會送到「BLoC」裡進行處理最後送到接收Steam的widget。
如同前面講過 BLoC 是一種design pattern ,所以他是可以在不使用「bloc」、「flutter_bloc」這兩個套件下被實作的出來的。
首先我們先來創立一個檔案來放 BLoC相關的東西。
import 'dart:async';
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterBLoC {
CounterBLoC() {
_counterEventController.stream.listen(_count);
}
int _counter = 0;
_count(CounterEvent event) {
if (event is IncrementEvent) {
counterSink.add(++_counter);
} else if (event is DecrementEvent) {
counterSink.add(--_counter);
}
}
final _counterStreamController = StreamController<int>();
StreamSink<int> get counterSink => _counterStreamController.sink;
Stream<int> get streamCounter => _counterStreamController.stream;
final _counterEventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink {
return _counterEventController.sink;
}
dispose() {
_counterStreamController.close();
_counterEventController.close();
}
}
首先會先看到
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
這邊就是所謂的「事件」也就是這個BLoC能夠處理的事件就只有這些。
接下來我們接著來宣告這個BLoC處理Stream的相關實作
final _counterStreamController = StreamController<int>();
StreamSink<int> get counterSink => _counterStreamController.sink;
Stream<int> get streamCounter => _counterStreamController.stream;
final _counterEventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink {
return _counterEventController.sink;
}
我們利用 _counterStreamController
來作為更新狀態及畫面更新的控制器,_counterEventController
則是來接收外部事件用。
然後實作我們的計數邏輯:
int _counter = 0;
_count(CounterEvent event) {
if (event is IncrementEvent) {
counterSink.add(++_counter);
} else if (event is DecrementEvent) {
counterSink.add(--_counter);
}
}
當我們的事件是 IncrementEvent
時向我們的_counterStreamController
的 sink傳入加一後的值,如果是 DecrementEvent
則傳入減一後的值。
最後則是讓我們這個BLoC 實例化後,去監聽 _count
CounterBLoC() {
_counterEventController.stream.listen(_count);
}
//省略
final bloc = CounterBLoC();
//省略
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
onPressed: () {
bloc.counterEventSink.add(DecrementEvent());
},
child: Text('-1')),
OutlinedButton(
onPressed: () {
bloc.counterEventSink.add(IncrementEvent());
},
child: Text('+1')),
],
),
StreamBuilder(
stream: bloc.streamCounter,
initialData: 0,
builder: (context, snapshot) {
return Center(child: Text(snapshot.data.toString()));
})
],
),
這邊我們就先實例化 CounterBLoC
然後將實作兩個按鈕然後將他們的 onPressed
時會觸發
bloc.counterEventSink.add(事件)
。
然後在用 StreamBuilder
來監聽 bloc.streamCounter
的變化來進行畫面重新渲染。
整個流程就會是:
按按鈕 → 送出事件到 counterEventSink
→ counterEventSink
收到事件後會送出值到 counterSink
→ streamCounter
響應變化。
今天的程式碼:
https://github.com/zxc469469/flutter_rest_api_playground/tree/Day27
明天就會開始使用套件來實作BLoC
參考資料: