【Flutter】flutter_blocについてまとめる

flutter_bloc公式ドキュメント→ https://pub.dev/packages/flutter_bloc

flutter_blocとは

✅ blocとcubitをFlutterに簡単に統合できるWidget
✅ blocパッケージで動作されるように構成されている
✅ flutter_blocパッケージによってexportされた全てのWidgetはCubitインスタンスとBlocインスタンス両方を統合する
✅ Blocは起こりうる状態変化のタイミングを規制し、状態を変化させる単一の方法を実行することで状態変化を予測可能にしている

Blocを採用する理由

bloc公式ドキュメント→ https://bloclibrary.dev/#/

✅ Blocはpresentation(UI, Widget, BLoC)からbusiness logic(UseCaseであるビジネスロジックに当たる部分)を簡単に分離できるため、テストが容易で再利用できるコードを高速に作成できる
✅ 本番品質のアプリケーションを構築する場合、状態管理が重要になる
⇨開発者は以下のことができる環境を作ることが重要

  • アプリケーションがどのような状態にあるかをいつでも把握することができる
  • アプリケーションが適切に動作、応答していることを確認するのに、全てケースを簡単にテストすることができる
  • データドリブンな意思決定が行えるように、アプリ内の全てのユーザーインタラクションを記録できる
  • 可能な限り効率的に動作し、アプリケーション内外の両方でコンポーネントを再利用できる
  • 全ての開発者が同じパターン、規則に従って単一のコードベース内でシームレスに作業できる
  • 高速かつ反応性の高いアプリ開発ができる

✅ Blocは上記のようなニーズを満たすように設計されており、次の3つのコアバリューを核に設計されている

Simple: 理解しやすく、様々なスキルレベルの開発者が使用可能
Powerful: より小さなコンポーネントで構成することで非常に複雑なアプリケーション作成を支援する
Testable: アプリケーションのあらゆる側面を簡単にテストできるため

Core Concepts (package:bloc)

blocの概念を理解するためにカウントアップするアプリを例に見てきます。
それに伴い、必要な知識も補足に記述していきます。

package:blocの導入

Installation

blocを導入する前に依存関係にあるパッケージのインストールが必要なので、
必ずbloc公式ドキュメントで確認します。

Import

インストールが完了したら、dartファイルにインポートしてきます。

Streams(事前知識)

✅ 連続性のある非同期データを生成するもの
✅ パイプに流れる水で例えると、”水は非同期データ”で”パイプがSteam”

👇 一番簡単なStreamを返す関数を見てみる。numbersリストがtoAdd関数に入り、Stream Controllerを通過してStreamとして返します。

👇上記のコードをasync*, yieldで作ってみます

✅ Dartでは”async*“(asyncジェネレータ関数)を記述することでStreamを生成することができる
✅ asyncの戻り値→Future、async*の戻り値→Streamの違いに注意
✅ yieldはasync*関数のみで使える、通常の関数におけるreturnの代わりであり、Streamを返す

countStream関数でasync*, yieldを使い1,2,3…のStreamを生成し、sumStream関数で生成したStreamを受け取り全ての値を合計したあと、その計算結果をsumとして返します。streamとして受け取った値をイテレートする場合は非同期処理にする必要があります。

ここまででStreamsがどのように働くか理解できたと思います。
次にblocパッケージのコアコンポーネントである”Cubit”について学んでいきます。

Cubit

✅ Cubit: BlocBaseを継承するクラスであり、任意の型の状態を管理するために使われる
✅ 状態変化をトリガーに呼び出される関数を作る
✅ StateはCubitの出力であり、アプリケーションの状態一部を表す
✅ UIコンポーネントは状態を通知され、現在の状態に基づいてコンポーネントの一部を再描画できる

Creating a Cubit

👇 Cubitは以下のように生成される

✅ Cubitを作成するときは、Cubitが管理する状態の型(str, int,…)を定義する必要がある
✅ 複雑な状態管理を必要とする場合、型はプリミティブ型(int, bool,…)以外にもクラスも使用できる
✅ 初期状態の指定はsuper()の引数に渡し、呼び出すことで実行することができる
✅ 上記のようにCubitを生成することで、様々な状態でインスタンス化することができる

State Changes

✅ Cubitはemitを介して新しい状態を出力する機能が備わっている
✅ “emit“はCubit内でのみ使用可能な、保護されたメソッド
✅ CounterCubitはincrement関数をパブリックメソッドとして公開しており、このメソッドは外部から呼び出してCounterCubitに状態をインクリメントするように通知できる
✅ increment関数が呼び出されると、状態ゲッターを介してCubitの現在の状態にアクセスし、1を足した新しい状態を発行する

Using a Cubit

◆basic usage

✅ increment関数をを呼び出し状態変更トリガーすると、Cubit状態を0→1にする
✅ Cubitのclose()関数を呼び出すことで、内部状態のストリームを閉じる

◆Stream Usage

✅ Cubitはリアルタイムに状態更新を受信できるようにStreamを備えている
✅ CounterCubitをサブスクライブし、状態が更新される度にprint関数を呼び出す

Observing a Cubit

✅ increment関数より状態が更新されると、onChangeをオーバーライドすることでCubitの変更を監視することができる

◆BlocObserver

✅ 状態管理すべきCubitが複数ある場合、各々の状態変更に対して処理を行いたい場合に使う

Error Handling

✅ Cubitにはエラーが発生したことを示す”onError”メソッドが用意されている

Bloc

✅ BLoc: Business Logic Component
✅ Cubitのようにfunctionではなく、”event“に依存して状態変化をトリガーする
✅ Cubit同様、BlocBaseを拡張する→パブリックAPIを備えている、状態ゲッターを介していつでもブロックの状態にアクセスできる
✅ BlocはBloc内の関数を呼び出し、新しい状態を直接発行(emit)するのではなく、イベントを受け取り、受信イベントを発信状態に変換する

Creating a Bloc

✅ Blocの作成方法はCubitに似ているが、管理する状態を定義するのではなくBlocが処理できるeventを定義する
✅ ”event”はボタンプッシュやページロードなどのユーザーインタラクションへの応答として追加され、Blocへ入力される
✅ Cubit同様、superを介して初期状態をsuperクラスに渡すことで指定する必要がある
✅ Blocは新しい状態を直接更新せず、必ずイベントハンドラー内の受信イベントに応答して状態変化を出力する必要がある

State Changes

✅ Cubitのように関数で状態更新するのではなく、on<Event>APIを介してイベントハンドラーする
✅ イベントハンドラーは受信したイベントを発信できる状態に変換する役割を担う

Using a Bloc

Basic Usage

✅ incrementイベントを追加して状態変化をトリガーする

Stream Usage

✅ Cubitと同様Blocは特殊なStream型であり、Blocをサブスクライブして状態をリアルタイムで更新することができる

Observing a Bloc

✅ BlocはBlocBaseを継承しているため、”onChange“を使うことで状態変化をobserveすることができる
✅ Cubitがfunction駆動型に対して、Blocはイベント駆動型であるため、”onTransition“を呼び出すことで状態変化をトリガーした要因に関する情報も取得することができる
✅ 状態変化はTransitionといい、現在の状態、イベント、次の状態を含む
✅ Cubit同様、CounterBlocクラスでoverrideする方法と、BlocObserverを継承したクラスに記述する方法がある
✅ Blocインスタンスのユニークな機能として、新しいイベントがBlocに追加される度に呼び出せる”onEvent“をオーバーライドできる

Cubit vs. Bloc 使いわけ

Cubit Advantages

Simplicity : Cubitの最大の利点は『単純さ』
⇨状態管理するCubitを継承したクラスと、状態を変更するためのパブリックメソッドを定義するでだけで済み、理解が容易で関連するコード量が少ない
⇔一方でBlocは状態管理するBlocを継承したクラス、Eventクラス、EventHandlerの実装を定義する必要がある

Bloc Advantages

Traceability : Blocの最大の利点は『状態変化のシーケンス性(連続性)』
⇨ある状態変化をトリガーに引き起こされた別の状態変化など、遷移の正確な原因を把握することができる
⇨状態変化を引き起こすイベントを網羅的に捉えるために、イベントドリブンなアプローチを使うことの重要性は大きい(特にアプリの重要な機能に関わる状態 ex. Authentication)
Advanced Event Transformations :
⇨”buffer”, “debounceTime”, “throttle”などのリアクティブ演算子を利用する必要がある時、Blocには着信イベントフローを制御、変換できるイベントシンクが備わっている
⇨EventTransformerを実装することで、Blocでの着信イベントの処理方法を変更することができる

Core Concepts (package:flutter_bloc)

package:flutter_blocの導入

Installation

flutter_blocを導入する前に依存関係にあるパッケージのインストールが必要なので、
必ずflutter_bloc公式ドキュメントで確認します。

Import

インストールが完了したら、dartファイルにインポートしてきます。

Bloc Widgets

BlocBuilder

✅ Blocとbuilder関数を必要とするFlutterウィジェット
✅ 新しい状態に応じてウィジェットをビルドする
✅ builder関数は何度も呼ばれる可能性があり、状態に応じてウィジェットを返す純粋な関数
※ナビゲーション、ダイアログ表示など状態変化に応じて”実行”したい場合はBlocListenerを参照👇
✅ blocパラメータを省略するとblocProviderと現在のBuildContextを使って自動的検索を実行する
⇨単一のウィジェットにスコープされ、親BlocProviderやBuildContextを介してアクセスできないBlocにする場合は指定する

✅ buildWhen: builder関数が呼ばれるタイミングを制御したい場合に使用
✅ 前と現在とbloc状態を取得しbool値で返す
⇨bool値がTrueの場合は、状態と一緒にbuilderが呼ばれリビルドされる、Falseの場合はリビルドされない

BlocProvider

✅ BlocProvider.of<T>(context)を介して子ウィジェットにblocを渡す
依存性注入(DI:Dependency Injection)ウィジェットとして働くため、blocの単一のインスタンスをサブツリー内の複数のウィジェットに提供できる
✅ BlocProviderはblocがBlocProvider.of<BlocA>(context)を介して検索されるとcreateが実行されるため、blocを遅延して生成する(blocは自動的にクローズ処理される)
⇨この動作をoverrideしてcreateをすぐ実行するには”lazy: false“に設定する

✅ ウィジェットツリー内の新しい部分に既存のBlocを使えるようにすることができる
✅ 既存のBlocを新しいルートで使用する必要がある場合に最も使われる
✅ BlocProviderはBlocをcreateしないため自動的にはクローズしない

✅ Blocを受け取る方法は以下の2つがある

MultiBlocProvider

✅ 複数のBlocProviderを1つにマージすることで可読性向上させ、複雑な構造を回避できる

BlocListener

✅ BlocWidgetListenerとBlocを受け取り、Blocの状態変化に応じてlistener呼び出す
✅ ナビゲージョン、スナックバーの表示、ダイアログの表示などの状態変化を起こす必要がある機能に使われる
✅ listenerは初期状態を除いた状態変化ごとに一度だけ呼び出されるvoid関数
✅ blocを指定するとBlocProviderおよびBuildContextを介してアクセスできないBlocにアクセスできる

✅ listenWhen: ここに記載される条件がtureで返される時、listenerが走る

MultiBlocListener

MultiBlocProviderと同様

BlocConsumer

✅ 新しい状態に反応するためにbuilderlistenerを備えている
✅ ネストされたBlocListenerとBlocBuilderを簡潔なコードで表現したもの
✅ UIを再構築と、Blocの状態変化に対して別の処理を実行する両方が必要な場合にのみ使用する
✅ BuildWidgetBuilder, BuildWidgetListener, bloc指定、BlocBuilderCondition, BlocListenerConditionを取る

✅ オプションであるlistenWhenとbuildWhenを実装することでより、listener, builderが呼び出されるタイミングを細かく制御することができる

BlocSelector

✅ 状態変化に条件を付与してbuilder関数を走らせることができる
✅ Blocbuilderは状態変化があると必ずリビルドされるが、監視する値を制限(フィルタリング)することで、不要なビルドを回避することができる

RepositoryProvider

BlocProviderと用法は同じため省略

MultiRepositoryProvider

MultiBlocProviderと用法は同じため省略

Example of flutter_bloc