概要
Flutter では、アプリの UI を宣言的に構築するため、状態(State)を適切に管理しないと、
- 画面遷移時にデータが消える
- UI の更新が適切に行われない
- データの一貫性が保てない
といった問題が発生します。筆者はFlutterを最近触り始めたペーペーなため、学習始めで特にこんがらがりそうな状態管理についてまとめておこうと思います。
状態管理とは?
状態管理(State Management) とは、アプリのデータ(状態)を適切に管理し、必要なときに UI へ反映させる仕組みのことです。
例えば、以下のようなデータは「状態」として扱います:
- カウンターの値
- API から取得したデータ
- フォームの入力値
- ログイン情報
- ダークモードの ON/OFF
Flutter では、宣言的 UI を採用しているため、状態が変更されると UI の再描画が必要になります。そのため、適切な状態管理の手法を選択することが重要になります。
状態管理の分類
状態管理の手法は、大きく 2 つのカテゴリに分かれます。
-
単純な状態管理 Ephemeral State(画面内で完結する状態)
setState()
-
InheritedWidget
/InheritedModel
-
ChangeNotifier
+Provider
-
グローバルな状態管理 App State(アプリ全体で共有する状態)
Provider
Riverpod
-
Bloc
/Cubit
Redux
GetX
この分類を理解したうえで、どの方法を選ぶべきか検討する必要があります。
1. 単純な状態管理
setState()
setState()
は、StatefulWidget
内で使用される最も基本的な状態管理手法です。
例:カウンターアプリ
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Counter App")),
body: Center(child: Text("Counter: $_counter")),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
メリット・デメリット
✅ シンプルで理解しやすい
✅ 追加のパッケージが不要
❌ setState()
を呼ぶたびに StatefulWidget
全体が再描画される
❌ アプリが複雑になると管理が難しくなる
ChangeNotifier
+ Provider
Flutter 公式が推奨する状態管理の方法。ChangeNotifier
は、状態変更を通知するクラスで、Provider
は依存関係を管理するパッケージ。
例:Provider を使ったカウンターアプリ
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
class CounterModel extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider Counter")),
body: Center(
child: Text("Counter: ${context.watch<CounterModel>().counter}"),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterModel>().increment(),
child: Icon(Icons.add),
),
);
}
}
メリット・デメリット
✅ setState()
よりも適切に状態を管理できる
✅ パフォーマンスが良い(変更があった部分のみ更新)
❌ 小規模なアプリにはオーバースペックになることも
2. グローバルな状態管理
Riverpod
Flutter 公式が開発した最新の状態管理ライブラリ。Provider
の進化版。
例:Riverpod を使ったカウンターアプリ
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateProvider<int>((ref) => 0);
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text("Riverpod Counter")),
body: Center(child: Text("Counter: $counter")),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
メリット・デメリット
✅ グローバルな状態管理が簡単
✅ Provider
よりも DI(依存関係管理)がしやすい
❌ シンプルなアプリにはオーバースペック
まとめ
状態管理手法 | 用途 | 規模 |
---|---|---|
setState() |
単純な状態管理 | 小規模 |
Provider |
公式推奨 | 中規模 |
Riverpod |
依存関係管理が楽 | 中大規模 |
アプリの規模や設計に応じて使い分けるのが最も重要ですが公式も推奨しているようなので、Riverpodを学んでおいて損はないかな?
RiverpodはRiverpodではまりどころがあるようなので、さらに調査を進めたいと思います。
参考リンク