この話の対象者は概ねこんな方です。
- ITベンチャーやITスタートアップのメンバー
- Flutter初心者がチームにいる中級〜上級者
- コードレビュー・リファクタリング担当の方
- AI生成コードの品質を安定させたい方
- 「自由」より「保守性」を優先したい企業人
はじめに
現在のFlutterの開発において、状態管理の選択肢は非常に豊富です。Riverpod、BLoC、その他諸々...。しかし、現場で初心者や中途採用メンバーと開発を共にする中で、ある「問題」に直面しました。
「どのパッケージを使っても、いつの間にかアクロバティックなスパゲッティコードが量産される(管理コストが爆上がりする等も含む)」
(※念のためですが、RiverpodやBLoC自体を否定する意図はありません。熟練したチームではこれらは非常に強力な選択肢ですし、弊社でも使っています)
熟練したチームであればこれらのライブラリでも全く問題ありませんが、自由度が高いライブラリは、経験の浅いメンバーが使うと凶器になります。
ただ、誰でも最初は初心者ですし、過去に私自身も、それはもうカオスなコードを生産していました。
どこで更新されたか追えない状態、ビルドメソッド内での強引なsetState、そしてAIが生成した「動くけれどメンテナンス不能なコード」・・・。
これらを「教育」や「気合のレビュー」で解決するのはどう考えても限界があります。
また、これから先はAIの書くコードの割合が増えていくのは必然なので、「AIが書いたコードを人間が容易に読めるようにするための制約」もあったほうが良さげ。
そこで、「構造的に変なコードを書けなくする」 ためのガードレールとして、状態管理パッケージ SimpleAppState を作りました。
私の会社では、この春から全ての新規Flutterプロジェクトでこれを標準採用し、実運用しながら改良していく予定です。
(注:このパッケージのドキュメントは英語で書いてるので、日本語で読みたい場合はブラウザでGoogle翻訳推奨です)
- pub.dev: simple_app_state
- ドキュメント: SimpleAppState Docs
SimpleAppStateの核心:「スロット境界(Slot Boundary)」
既存の多くのライブラリは、ウィジェット内のどこからでもあらゆる状態にアクセスできる「便利さ」を追求しています。しかし、これはカオスの原因になります。
SimpleAppStateでは、「その画面が触っていいデータ」を事前に明示的に宣言しなければなりません。 これを「スロット境界」と呼んでいます。
このパッケージは、主にこの「スロット境界」パターンを強制させるためのパッケージとも言えます。
要するに、本パッケージは設計を安全性側に強く振り、その代わり「自由にどこからでも状態を触りたい」のような柔軟性は犠牲にした形になっています。
/// ui/app_state.dart
// ステート定義
final appState = SimpleAppState();
// スロットの定義
final userSlot = appState.slot<User>('user', initial: User());
final settingSlot = appState.slot<Setting>('setting', initial: Setting());
/// ui/pages/user_screen.dart
class UserScreen extends SlotStatefulWidget {
const UserScreen({super.key});
@override
List<StateSlot> get slots => [
userSlot, // ユーザー情報
settingsSlot, // 設定情報
];
@override
SlotState<UserScreen> createState() => _UserScreenState();
}
class _UserScreenState extends SlotState<UserScreen> {
@override
Widget build(BuildContext context) {
final user = userSlot.get();
final setting = settingsSlot.get();
return /* slotsの内容を使ったウィジェット */;
}
}
なぜ、わざわざ宣言させるのか?
- 設計と実装の「契約」: リーダーがこの枠組み(slots)だけを定義して渡せば、実装者はその範囲内でしかコードを書けません。「勝手に別の状態をいじる」ことが物理的に難しくなります。
-
レビューの高速化: 「この画面、なんで再描画されるの?」という疑問に対し、
slotsを見るだけで答えが出ます。ここに書かれているものが更新された時にそのウィジェットが再描画されるからです。 - 依存関係の可視化: 画面が何に依存しているかがコードの冒頭に明示されるため、設計の不備にすぐ気づけます。
現場で役立つ「規律」の数々
1. 読み書きの分離
状態の取得は slot.get()、更新は slot.set()または slot.update((oldCopy)=>newV) と明確に分かれています。
特に SimpleAppState では、update に渡される古い値を含め、実装者が触れる範囲が全てディープコピー済みであるため、初心者が参照渡しで予期せぬ副作用を起こすリスクを構造的に消しています。
参照が必要になるケースでは上級者向けのRefAppStateというのがあるので、実務では初心者にはSimpleAppStateのみを利用してもらい、まず初心者の経験値を上げていくという運用ができます。
実行速度を向上したい場合、上級エンジニアが全体をリファクタリングする際にRefAppState併用に変更すれば良いだけなので、ある種の早すぎる最適化も防げます。
2. テストの容易性
SimpleAppStateは、Notifier系などの状態が分散しがちな管理方式と異なり、状態を集中管理できるため、デバッグがかなり簡単になります。
標準機能で、定義したスロットの現在の状態を追跡できるので、いちいち色んなところでprintを書く必要は無いです。
Testing methods - SimpleAppState
AI時代に最適化した「専用プロンプト」も提供してます
最近はChatGPTやGeminiにコードを書かせるのが当たり前になりました。簡単なコードなら、手で記述するよりも早いですからね。
しかし、現状でAIに簡単な関数以上のものを生成させたい場合は、設計思想まで含めて制御しないと破綻しやすくなる傾向があります。
試験的ですが、SimpleAppStateは、AIに正しい作法でコードを書かせるための「公式プロンプト」をドキュメントで提供しています。
このプロンプトを会話の最初にAIへ投げて、ドキュメントのように指示すれば、破綻しづらく、パッケージの哲学に沿ったクリーンなコードを生成させられる可能性が上がります。
(※なおこのプロンプトは適宜アップデートが入ります)
おわりに
日本の開発現場(特に受託開発やチーム開発)には、海外のエンジニアとは異なる悩みも多いと思います。
そもそも採用プロセスが全然違っていて最初は完全初心者が入ってくるとか、「自由」よりも「メンテナンス性(仕様変更耐性を含む)」を優先したいとか。
あとはまあ、日本語でのサポートが無いと困るとか。
既存ライブラリを組み合わせてルールを作るより、最初から「変なことができない道具」を用意した方が現場では事故らない筈。ということで、このパッケージを作りました。
保守性の高いコードを目指している方は、よければ触って遊んでみてください。
ドキュメントやサンプルコードへのフィードバックも、もしよろしければ。
issueはもちろん日本語OKです。
- GitHub: simple_app_state
- Quick Start: 初心者向けガイド