@macoshita

Flutter でシルエットクイズアプリを作ってアーキテクチャとか考えた

Flutter

作ったもの

https://github.com/macoshita/silhouette_game

設計

いくつかの原則を守る

https://medium.com/flutter-jp/architecture-240d3c56b597 にある下記を遵守する。

記事では上 2 つが必須とあり、immutable は mutable のほうが分かりやすくなることも極稀にあるし、テストは常に書けるとも限らないのでわかるけど、単一責任の原則は必須として良いんじゃないかなーと思わなくはない。

Feature(機能) で切る

lib/features/ 以下に digital_ink_recognizer/ などディレクトリを掘ってそこにその機能絡みのファイルをすべて置くようにした。

最初は 1 ファイルで済むものはディレクトリを掘らないことにしていたが、むしろ見づらい気がしたので 必ずディレクトリを掘る ようにした。途中で .freezed.dart.g.dart が生えることになることも多く、これはそういうルールにしたほうが良いかも。

ディレクトリ内は 機能を提供するための riverpod provider とコンポーネントのセット という構成になっていて、例えば lib/features/game_mode 以下には現在選択中のゲームモード(ひらがなモード・カタカナモード)を取得できる provider と、ゲームモードを切り替えるスイッチを提供している。

Feature の粒度は SSOT の原則を守れる形にする。
例えば問題を提供する機能は lib/features/quiz にまとめていて、他のところで問題を取得するには quiz/ 以下の provider を使うようにしている。
逆に言えばそれを守れれば Feature 同士の依存関係は何も気にしてない。SSOT、単一責任の原則を守れていれば、そこの依存がゴチャついて困ることもないはず、という考え。

ただ、大人数で開発するときには「問題取得する provider まだ作られてないな、lib/features/question/ 作ろう」となりそうなので、lib/data/ に掘ることにするなどのルール決めが必要そうな気もするけど、じゃあ gameModeProvider みたいな StateProvider も lib/data/ に置くのか?というとうーん。
「コンポーネントがないモノは機能としない」という分け方でも良いのかも?

Provider を細かく分ける

単一責任の原則を意識して、しかも機能で切るとなると自然とそうなるけど、でかい Provider は危険信号と捉えることにしていた。
ただ、この粒度は割と悩ましく、特に悩んだのは quizProvider で、最終的には「問題セット」「今の問題文」「今の問題文字」をまとめた state を返すようにしたが、それぞれを別の Provider で提供しても良かったような気もしている。
まとめた理由は問題セット→今の問題文→今の問題文字と拾ってくるところがピタゴラスイッチ過ぎると感じたのと、次の問題文・次の問題文字に進むためのメソッドをまとめたいと思ったのとで、どっちも若干決め手には欠ける。

ライブラリ

多分、今後しばらくは、新規アプリを作るとき、このセットを初手でいれることになりそうだと思った。

小ネタ

まとめ

それなりに大規模開発に活かせそうな知見も得られたと思う。
次は体温・お薬記録アプリ(バックエンドに Firebase)を作ろうと思うので、もうちょい濃い知見が得られるといいなと思う。