Flutter 状態管理
状態管理は Flutter 開発における中核的な概念です。
このセクションでは、setState、Provider、ChangeNotifier を含む Flutter 組み込みの状態管理方法を紹介します。
状態管理とは
状態管理とは、アプリケーション内のデータの変化と UI の更新を管理する仕組みです。データが変化したときに UI もそれに合わせて更新する必要があります。これが状態管理が解決する問題です。
| 状態の種類 | 説明 | 例 |
|---|---|---|
| ローカル状態 | 単一の Widget 内の状態 | 入力欄のテキスト、アニメーションの進捗 |
| 共有状態 | 複数の Widget で共有する必要がある状態 | ユーザーログイン情報、テーマ |
| アプリ状態 | アプリ全体でアクセスする必要がある状態 | 買い物かごの内容、ユーザー設定 |
ローカル状態 - setState
単純なローカル状態には、StatefulWidget と setState で十分です。
例:カウンターの例
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
// ローカル状態
int _count = 0;
// カウントを増やす
void _increment() {
setState(() {
_count++;
});
}
// カウントを減らす
void _decrement() {
setState(() {
_count--;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('カウント: $_count', style: const TextStyle(fontSize: 32)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _decrement, child: const Text('-')),
const SizedBox(width: 20),
ElevatedButton(onPressed: _increment, child: const Text('+')),
],
),
],
);
}
}
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
// ローカル状態
int _count = 0;
// カウントを増やす
void _increment() {
setState(() {
_count++;
});
}
// カウントを減らす
void _decrement() {
setState(() {
_count--;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('カウント: $_count', style: const TextStyle(fontSize: 32)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _decrement, child: const Text('-')),
const SizedBox(width: 20),
ElevatedButton(onPressed: _increment, child: const Text('+')),
],
),
],
);
}
}
Provider 状態管理
Provider は Flutter 公式が推奨する状態管理ソリューションの1つで、コンポーネント間で共有される状態の管理に適しています。
Provider のインストール
pubspec.yaml に依存関係を追加します:
dependencies: provider: ^6.0.0
ChangeNotifier パターン
例:Provider + ChangeNotifier
// 1. データモデルを定義(ChangeNotifier を継承)
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// リスナーに状態が変更されたことを通知
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
// 2. アプリの最上位でデータを提供
void main() {
runApp(
// CounterModel を子コンポーネントに提供
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
// 3. コンポーネント内でデータを使用
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
// CounterModel を読み取る
final counter = context.watch<CounterModel>();
return Column(
children: [
Text('カウント: ${counter.count}', style: const TextStyle(fontSize: 32)),
ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('増加'),
),
],
);
}
}
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// リスナーに状態が変更されたことを通知
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
// 2. アプリの最上位でデータを提供
void main() {
runApp(
// CounterModel を子コンポーネントに提供
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
// 3. コンポーネント内でデータを使用
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
// CounterModel を読み取る
final counter = context.watch<CounterModel>();
return Column(
children: [
Text('カウント: ${counter.count}', style: const TextStyle(fontSize: 32)),
ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('増加'),
),
],
);
}
}
複数 Provider の組み合わせ
例:複数 Provider の使用
void main() {
runApp(
MultiProvider(
providers: [
// ユーザー情報 Provider
ChangeNotifierProvider(create: (_) => UserModel()),
// テーマ Provider
ChangeNotifierProvider(create: (_) => ThemeModel()),
// 買い物かご Provider
ChangeNotifierProvider(create: (_) => CartModel()),
],
child: const MyApp(),
),
);
}
// 変化を監視する必要がない場合は context.read を使用
class SomeWidget extends StatelessWidget {
const SomeWidget({super.key});
@override
Widget build(BuildContext context) {
// 読み取るが監視はしない
final user = context.read<UserModel>();
return ElevatedButton(
onPressed: () => user.logout(),
child: const Text('ログアウト'),
);
}
}
runApp(
MultiProvider(
providers: [
// ユーザー情報 Provider
ChangeNotifierProvider(create: (_) => UserModel()),
// テーマ Provider
ChangeNotifierProvider(create: (_) => ThemeModel()),
// 買い物かご Provider
ChangeNotifierProvider(create: (_) => CartModel()),
],
child: const MyApp(),
),
);
}
// 変化を監視する必要がない場合は context.read を使用
class SomeWidget extends StatelessWidget {
const SomeWidget({super.key});
@override
Widget build(BuildContext context) {
// 読み取るが監視はしない
final user = context.read<UserModel>();
return ElevatedButton(
onPressed: () => user.logout(),
child: const Text('ログアウト'),
);
}
}
状態の引き上げ - Lifting State Up
複数の子コンポーネントが状態を共有する必要がある場合、状態を最も近い共通の親コンポーネントに引き上げることができます。
例:状態の引き上げ
// 親コンポーネントが状態を管理
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
// 共有する状態
bool _isEnabled = false;
// コールバックメソッド
void _toggleEnabled() {
setState(() {
_isEnabled = !_isEnabled;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 子コンポーネント A
ChildWidgetA(
isEnabled: _isEnabled,
),
// 子コンポーネント B
ChildWidgetB(
isEnabled: _isEnabled,
onToggle: _toggleEnabled,
),
],
);
}
}
// 子コンポーネント A(状態のみ表示)
class ChildWidgetA extends StatelessWidget {
final bool isEnabled;
const ChildWidgetA({super.key, required this.isEnabled});
@override
Widget build(BuildContext context) {
return Text('状態: ${isEnabled ? "オン" : "オフ"}');
}
}
// 子コンポーネント B(状態を制御)
class ChildWidgetB extends StatelessWidget {
final bool isEnabled;
final VoidCallback onToggle;
const ChildWidgetB({
super.key,
required this.isEnabled,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onToggle,
child: Text(isEnabled ? 'オフ' : 'オン'),
);
}
}
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
// 共有する状態
bool _isEnabled = false;
// コールバックメソッド
void _toggleEnabled() {
setState(() {
_isEnabled = !_isEnabled;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 子コンポーネント A
ChildWidgetA(
isEnabled: _isEnabled,
),
// 子コンポーネント B
ChildWidgetB(
isEnabled: _isEnabled,
onToggle: _toggleEnabled,
),
],
);
}
}
// 子コンポーネント A(状態のみ表示)
class ChildWidgetA extends StatelessWidget {
final bool isEnabled;
const ChildWidgetA({super.key, required this.isEnabled});
@override
Widget build(BuildContext context) {
return Text('状態: ${isEnabled ? "オン" : "オフ"}');
}
}
// 子コンポーネント B(状態を制御)
class ChildWidgetB extends StatelessWidget {
final bool isEnabled;
final VoidCallback onToggle;
const ChildWidgetB({
super.key,
required this.isEnabled,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onToggle,
child: Text(isEnabled ? 'オフ' : 'オン'),
);
}
}
関連情報状態管理の選択はアプリの規模によって異なります。シンプルなアプリでは setState、中規模のアプリでは Provider、複雑なアプリでは Riverpod、BLoC、GetX などのソリューションを検討できます。