🌐

[Flutter] Redux 入門

2022/05/09に公開約4,900字

ReduxはFlutterで用いられる状態管理手法の一つです。元々はReactで使用されている状態管理を行うためのライブラリで、各コンポーネントに対して簡単に状態 (State)を共有することができます。Reduxの考え方はFlutterにも適用することができ、各ウィジェットに対して状態 (State)を共有することができます。

https://www.youtube.com/watch?v=60_2HlagOzg

Reduxの3原則

Reduxには守らないといけない3つのルールがあります。

  1. 状態 (State)は一つのオブジェクトに集約する。
    アプリの状態を一つのオブジェクトに集約することで、状態の管理をシンプルにすることができます。
  2. 状態 (State)は読み込み専用にする。
    アプリケーションの状態を読み込み専用にすることで、予期せぬ状態の変更を防ぐことができます。
  3. 状態 (State)の変更は関数を通してのみ行う。
    アプリケーションの状態変更は関数を通してのみ行うことで「2」を実現することができ、状態の更新経路を一つの関数にすることで、状態変更経路をシンプルにすることができます。

Reduxの構成要素

ReduxではStoreActionReducerという三つの要素が重要な役割を果たします。

Store

Reduxでは「Store」と呼ばれるオブジェクトの中で、アプリケーションの状態を管理します。また、「Store」はアプリケーション内に一つだけ存在します (原則1)。

Action

画面に表示されるボタンがタップされるといったイベントが発生するとView (画面)はActionというオブジェクトを作成します。このオブジェクトを後述するReducerに渡します。

Reducer

Actionの内容に基づいてReducerが新しい状態を作成し、Storeで管理している状態に反映します (原則3)。状態は読み込み専用となるため (原則2)、現在の状態に変更を加えることはせず、状態を新しく作成します。

Reduxでの状態変更フロー

Reduxでの状態変更は単一方向に行われます。

  1. View (画面)でイベントが発生
  2. View (画面)で発生したイベントに対応するActionを生成
  3. ActionをReducer関数に送信
  4. Reducer関数はActionを解析して状態 (State)を更新
  5. 状態の変更を画面に反映

実装

カウンターアプリをReduxを用いて開発してみます。画面中央にカウント数を表す数字が二つ、カウント数をインクリメント/デクリメントする為のボタンが四つ表示されるシンプルなアプリです。

ソースコードは以下に置いています。

https://github.com/NAOYA-MAEDA-DEV/flutter_redux_sample

パッケージのインストール

flutter_reduxパッケージをインストールするため、pubspec.yamlに以下のコードを記述します。

Stateクラスの作成

カウント数を保持するためのint 型変数counterA、counterBを定義しています。読み込み専用にするため、final 修飾子を付加しています。

今回のアプリケーションではStateクラスは一つのみですが、実践的なアプリケーションを開発する時は、Stateクラスが複数になってきます。Stateクラスが複数になった時に対応するため、Stateクラスを一度にまとめて初期化するためにRootState というクラスを用意します。RootState クラスでは全てのStateクラスをメンバ変数として保持します。

Actionクラスの作成

Actionクラスは画面で発生したイベントを区別するためのクラスです。今回用意したイベントは以下の四つです。

  • CounterAをインクリメント
  • CounterAをデクリメント
  • CounterBをインクリメント
  • CounterBをデクリメント

画面で発生したイベントを区別するためのクラスなので、クラス内は空で問題ありません。

Reducer関数の作成

Stateクラスを更新するReducer関数を作成します。Reducer関数は (元の状態(State), アクション) => 新しい状態(State) といった形式の純粋関数にします。 引数のアクションに対して、アクションの種別を判定して、新しい状態を生成しています。

StoreProviderウィジェットの配置

Store クラスオブジェクトをWidgetツリー全体で使用することができるようにするために、flutter_reduxプラグインで提供されているStoreProvider ウィジェットを上位ツリーに配置します。
StoreProvider ウィジェットは下位ツリーのウィジェットにStore クラスオブジェクトを渡せるようにするためのウィジェットです。store 引数にはStore クラスオブジェクトを指定し、child 引数には表示したいウィジェットを指定します。
今回はMyHomePageウィジェット以下でStore クラスオブジェクトを参照できるようにしています。

下位ツリーからStoreクラスオブジェクトにアクセス

下位ツリーからStore クラスオブジェクトにアクセスする手段はStoreBuilder ウィジェットを使用する方法とStoreConnector ウィジェットを使用する方法があります。

StoreBuilder

StoreBuilder ウィジェットの引数に指定したビルダー関数の引数からStore クラスオブジェクトにアクセスすることができます。Store クラスオブジェクトに変更が入った時、StoreBuilder ウィジェットの引数に指定したビルダー関数はリビルドされます。つまりStore クラスオブジェクトに変更が入った時 (= counterA、counterBのいずれかに変更が入った時)に、ビルダー関数で生成するウィジェットの表示を更新することができます。サンプルコードではStoreBuilder ウィジェットの引数に指定したビルダー関数でWidgetAを生成するようにしています。

StoreConnector

StoreConnector ウィジェットは特定の状態が変更された時、StoreConnector ウィジェットの引数に指定したビルダー関数をリビルドさせることができます。StoreConnector ウィジェットのconverter 引数には、引数にstore を渡して、変更を検知したい変数を返す関数を指定します。さらに、ここで指定した関数の返り値はbuilder 引数に指定したビルダー関数の引数となります。
サンプルコードではconverter 引数に指定した関数の返り値をcounterBにすることで、builder 引数に指定したビルダー関数の引数にcounterBが渡され、counterBに変更が入った時のみ、StoreConnector ウィジェットの引数に指定したビルダー関数をリビルドさせるようにしています。サンプルコードではStoreConnector ウィジェットで指定したビルダー関数でWidgetBを生成するようにしています。

実行結果

ボタンをタップする度に画面中央に表示されている数字がインクリメントされています。

[Increment Count A] / [Decrement Count A]ボタンをタップした時は、コンソールに'Build WidgetA'が表示されています。これはCounterStateオブジェクトのcounterA変数が変更されたことをStoreBuilder ウィジェットが検知し、ビルダー関数が実行された結果WidgetAがリビルドされていることが理由です。また、counterA変数に変更が入ってもコンソールに'Build WidgetB'が表示されず、WidgetBはリビルドされていないことがわかります。

[Increment Count B] / [Decrement Count B]ボタンをタップした時は、コンソールに'Build WidgetA'、'Build WidgetB'が表示されています。これはCounterStateオブジェクトのcounterB変数が変更されたことをStoreBuilder ウィジェット、StoreConnector ウィジェットが検知し、両ウィジェットのビルダー関数が実行された結果、WidgetA、WidgetBがリビルドされていることが理由です。

Discussion

ログインするとコメントできます