iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
Modern Web

Flutter web 的奇妙冒險系列 第 27

Day 27 | 狀態管理 - BLoC基本介紹

在剛開始學習Flutter時如果讀到有關狀態管理的文章大部分都會是與「BLoC」相關的內容,雖然真的是有點複雜,但感覺還是得嘗試看看「BLoC」到底有什麼優點可以是Flutter中最多人推崇的架構(但也許現在的風向是GetX比較受歡迎)

那什麼是BLoC呢?

所謂 BLoC 就是 Business Logic Components的縮寫,BLoC其實是一種 design pattern,而不只是套件。他的目標是在分離「畫面」以及「商業邏輯」,其實就跟我們之前在講狀態管理時的目標一樣。

BLoC的核心概念是將所有「事件」都視作stream:

https://ithelp.ithome.com.tw/upload/images/20211010/201129069Mlv9OatL9.png

我們透過送出「事件」然後會送到「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 的變化來進行畫面重新渲染。

整個流程就會是:
按按鈕 → 送出事件到 counterEventSinkcounterEventSink 收到事件後會送出值到 counterSinkstreamCounter 響應變化。


今天的程式碼:
https://github.com/zxc469469/flutter_rest_api_playground/tree/Day27

明天就會開始使用套件來實作BLoC


參考資料:

  1. https://medium.com/flutter-community/flutter-bloc-with-streams-6ed8d0a63bb8
  2. https://blog.zcychen.com/post/archived/2020/03/bloc-design-pattern/
  3. https://juejin.cn/post/6844903821336903694#heading-1

上一篇
Day 26 | 共享 MobX store with get_it
下一篇
Day 28 | 狀態管理-從官方範例來看如何使用BLoC
系列文
Flutter web 的奇妙冒險30

尚未有邦友留言

立即登入留言