FlutterアプリのアーキテクチャProviderの使い方を解説【初心者脱却シリーズ】

どうもこんにちはコウヘイです。
本シリーズは、Flutterについて完全初心者の私がFlutterをある程度使えるようになるまで頑張るをテーマに日々の作業ログを記録する内容になります。
以下のような疑問を持ちました。
Flutterアプリを作成するにあたってどのアーキテクチャを使用して開発するのか決める必要があります。2020年現在どれが一番推奨されているのか、そして使い方を知りたいです。
今回はFlutterのアーキテクチャProviderについて実際に使ってみたのでまとめたいと思います。
それでは本題。
今回は以下の内容になります。
著者情報
ちなみにですが、私は5年以上IT系エンジニアとして働いており、主にJavaを主戦場にしています。Webアプリケーションと業務系のアプリケーションの経験を持つごく普通のエンジニアです。
FlutterアプリのアーキテクチャProviderの使い方を解説
以下、手順になります。
- Provider関連のパッケージを取得する
- ViewModel作成
- View作成
順に解説します。
Provider関連のパッケージを取得する
まず、Provider関連パッケージを使用するために、pubspec.yamlに以下を追加します。
(省略) dependencies: flutter: sdk: flutter provider: ^4.3.1 ←★ここから freezed_annotation: state_notifier: ^0.5.0 flutter_state_notifier: ^0.4.2 ←★ここまで (省略)
pubspec.yamlとは
ライブラリのバージョン管理、また公開されていないライブラリ(自分で作成したパッケージとか)を参照することができます。
上記の例で言うと、dependenciesの子要素に使いたいパッケージをバージョン指定で記述します。
pubspec.yamlに使用したいパッケージを記述しただけでは使用することができません。以下のコマンドを実行します。
flutter packages pub get
※pubspec.yamlがあるディレクトリで実行する必要があります。
ViewModel作成
ViewModelは状態とロジックを記述する部分になります。
今回はStateNotiferクラスを使用したViweModelを作成します。
よく、ChangeNotiferクラスを使う場合をネットでは見ますが、StateNotiferクラスとの違いは以下です。
(どちらもProviderパッケージ作成者が作成したものになります。)
- ChangeNotifer:「View」と「状態&ロジック」を切り離すことができる、変更通知をするためにはnotifyListeners()を呼ぶ必要がある、複数の変数を定義できる
- StateNotifer:「View」と「状態」と「ロジック」を切り離すことができる、変数はstate1つのみ
つまり、ChangeNotiferクラスでいう「状態」と「ロジック」を切り離すことができるのがStateNotiferクラスです。
このことにより、より切り分けがはっきりするのでコードも読みやすくなります。
それでは、StateNotiferクラスを使用したViewModelの作成手順は以下です。
- Stateクラス(状態)を作成
- StateNotiferクラス(ロジック)を作成
順に説明します。
Stateクラス(状態)を作成
以下、Stateクラスの例です。
import 'package:flutter/material.dart'; /* 状態クラス * @immutableアノテーションを付与することで、フィールド変数を変更することができないようにしている * フィールド変数はViewクラスで動的に変わる変数を定義する */ @immutable class MainState { MainState({ this.side_index, this.side_visible, this.bottom_index, }); final int side_index; final bool side_visible; final int bottom_index; }
StateNotiferクラス(ロジック)を作成
以下、StateNotiferクラスの例です。
import 'package:calendar/states/main_state.dart'; import 'package:state_notifier/state_notifier.dart'; /* StateNotifierクラスを継承したクラスを作成 */ class MainStateNotifier extends StateNotifier<MainState> { /* 親クラスのコンストラクタに1つの状態変数を受け渡す * 今回は別途用意した状態クラスを状態変数としている */ MainStateNotifier() : super(MainState(side_index: 0, side_visible: false, bottom_index: 0)); // Viewクラスで呼び出される関数、サイドバーのアイテムをタップしたときに呼び出される void onSideItemTapped(int index) => state = MainState(side_index: index, side_visible: state.side_visible, bottom_index: state.bottom_index); // Viewクラスで呼び出される関数、ボトムバーのアイテムをタップしたときに呼び出される void onBottomItemTapped(int index) => state = MainState(side_index: state.side_index, side_visible: state.side_visible ,bottom_index: index); // Viewクラスで呼び出される関数、サイドバーの表示有無を切り替える void changeSidebarVisible() => state = MainState(side_index: state.side_index, side_visible: !state.side_visible, bottom_index: state.bottom_index); }
View作成
Viewではウィジェットを作成します。
前章で作成したViewModelを使用するためには、使用するウィジェットよりも上位で使用するViewModelを指定する必要があります。(重要)
例えば、以下のように指定します。
(省略) void main() => runApp( StateNotifierProvider<MainStateNotifier, MainState>( create: (_) => MainStateNotifier(), // 使用したいViewModelを指定 child: MyApp(), // 子ウィジェットで上記で指定したViewModelを使用できるようになる ), ); (省略)
StateNotifierProviderウィジェットではJavaのSpringフレームワークで言うDIコンテナにViewModelを登録している感じだと思います。
FlutterでDIコンテナと呼ぶかはわかりませんが、コンテナに登録したクラスはDI(依存性の注入)することで使用することが可能になります。
次は子ウィジェットのMyAppでViewModelを使用する方法について解説します。
子ウィジェットで値を取得するためにはProvider.of関数を使用します。
Provider.of<MainState>(context, listen: true)
上記の例ではMainStateクラスのインスタンスを取得しています。
よって、MainStateクラスのフィールドにアクセス可能です。
また、以下のようにMainStateNotifierクラスのインスタンスも取得できます。
Provider.of<MainStateNotifier>(context, listen: true)
MainStateNotifierクラスのインスタンスを取得することでMainStateNotifierクラスの関数を利用することができます。
Provider.of関数の引数listenの詳細は以下です。
- true
デフォルトの値。
変更が通知されるとリビルドが起こります。 - false
リビルドを防げます。
値の変更に応じた処理が不要な箇所では false にするとパフォーマンスが上がる。
Providerの使い方については以上になります。
余談①:Providerが推奨されている理由
2019年以降Providerを使用したアプリケーション開発が推奨されています。
(それまではBLoCだったみたいです。)
理由は以下になります。(足りてないかもしれませんが、自分が実感している内容になります。)
- DIを使って状態をWidgetにインジェクションするので各Widgetのコンストラクタに値を引き渡す必要がない(疎結合)。
- ChangeNotifierやStateNotifierを使用してWidgetに状態の更新を通知することができ、StatelessWidgetでも画面を更新することが可能になる。(状態とロジックをWidgetから分離)
- 状態をBindingしている箇所だけ更新されるのでパフォーマンスが向上
実際に他のアーキテクチャを使用したアプリケーション開発はしたことがないので比較はできませんが、JavaのSpringを使用したアプリケーション開発を通してDIの便利さはわかっているつもりです。
Springを通してメリットを理解していたこともあり、私はまずはじめにProviderを使用することを決めました。
JavaのSpringフレームワークについてですが、以前にDIについてのメリットを記事にしています。違う言語ですが参考になると思うので、ぜひ見てみてください。
余談②:その他のアーキテクチャも使ってみたい
私の知る限りFlutterを使用したアプリケーション開発では以下のアーキテクチャがあります。
- ScopedModel:よくわからない
- BLoC:Provider推奨前に推奨されていたアーキテクチャ
- Redux:Javascritでよくつかわれているアーキテクチャ
- Provider:今回対象アーキテクチャ
今回はProviderについてまとめました。
他のアーキテクチャについても使用してみたいと考えています。
(次はBLoCかな)
といっても、まだProviderアーキテクチャを使ったアプリケーションを作りきっていないので、ある程度まで作成してからにします。
まとめ
以上、FlutterアーキテクチャのProviderについてまとめました。
個人的にはDIについてJavaのSpringフレームワークを使用したことがあったのでスムーズにProviderを導入することができました。
今回はDIについてですが、いいものは新しい言語に反映されるのかなとも思いました。(当たり前か笑)
それはさておき、これで開発環境は整えられたので、今度は実際にアプリケーションを作成していきながらノウハウを蓄積していきたいと思います。
(カレンダーアプリを作ろうかと思っています。)
これからも個人開発がんばります。
どなたかの参考になれば幸いです。
