「今ならこうする」をシリーズ化しようと思ってカテゴリを作った。「今ならこうする」は僕基準であって全員そうすべきという話ではない。今回もそう。
Flutterにはprovider packageというのがあり、公式でもprovider + ChangeNotifierの構成がドキュメントにあったりするし、なんなら A recommended approach. として紹介されている。 それくらいprovider packageはよく出来ていて僕もFlutterをやり始めた当初から使っている。
Simple app state management - Flutter
考え
provider packageは便利だけど問題や足りないこともあって困る。
なぜ
DIがめんどくさい
provider packageはBuildContextに依存している。BuildContextに依存していないViewModelのインスタンスを作るときは以下のようにcontext.read()で値を取得しインスタンスを作成して下位のWidgetで利用できるようにしている。
class HogeScreen extends StatelessWidget { static Widget _newScreen() { return ChangeNotifierProvider<HogeViewModel>( lazy: false, create: (context) { return HogeViewModel( rpcClient: context.read(), logger: context.read(), firebaseAuth: context.read(), ); }, child: HogeScreen._(isInitialRoute), ); } }
このやり方には以下のような問題がある。
- インスタンスの作成するたびに毎回自分でcontext.read()する必要がありめんどくさい
- 単体テスト時にBuildContextには依存できないので別のライブラリに依存する必要があり値の2重管理になる
またぼくたちは利用していないけど、providerにはLocatorという型がある。 インスタンスを作るときにLocatorを渡して、ViewModelの中で値を取得しちゃおうというやり方である。この方法だとインスタンスを作成するたび毎回自分でcontext.read()しなくていいので楽ではある。
Locator typedef - provider library - Dart API
しかしLocatorを利用すると以下のような問題が起きる。
- ViewModelが何に依存しているか外からみて明確ではないので実装を見る必要がある。また依存に変更があったときに変更漏れが起きやすい
- インスタンス作成時ではなく値が必要なタイミングで取得することが出来るのでその場合エラーになるタイミングが遅れて気づきにくい
- 単体テスト時に別ライブラリに依存して値の2重管理になる問題は解決していない
なのでLocatorは使わないのがよいと思っている。
またproviderの話ではなくproviderの使い方の話だけど、readを多用してしまったのは間違いだったなと思っている。watchすべきだった。
値が未定義の場合、実行時エラーになる
Providerは値を定義していないときに実行時にエラーになる。定義されていないときはコンパイルエラーになるべき。
devtoolが見にくくなる
Providerは値を定義するときにWidgetとして定義するので、扱う値が多ければ多いほどdevtoolで見るとlayout treeがProviderで埋まってしまう。
今ならどうするか
作者が同じRiverpodを使う。
Riverpodは上記のprovider packageの問題点はすべて解決している。
ただし、APIが安定していない。APIの変更自体はmigrate toolが提供されるのである程度問題ないが、migrate toolで対応できない部分もあるかもしれないので手で直したりするコストは必要になる。またドキュメントも十分とは言えずコードやイシューを読んだりする必要があったりする。
ただ僕はそういうのは自分や人が時間をかければ解決する問題だと思っていて、provider packageの問題は時間が経っても解決しない根本的な仕様なので、解決しそうなほうをとりたいという感じ。
まとめ
1 on 1をしていたときにproviderってどうなんですかと聞かれたので書いた。
ぼくはRiverpod推しではあるが、provider packageにも当然価値はあって例えばドキュメントが充実していたり、APIが安定していたり、公式でdevtoolの対応が入っていたりする。provider packageを使うのは全然悪い選択ではないし、むしろ状況によっては非常にいい選択になりえるとも思っている。
しかし、ある程度Flutterに慣れた人はRiverpodを選択すると上記の課題は解決されるのではと思っている。
次回は、TextStyleの共通化について。