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('+')),
          ],
        ),
      ],
    );
  }
}

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('増加'),
        ),
      ],
    );
  }
}

複数 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('ログアウト'),
    );
  }
}

状態の引き上げ - 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 ? 'オフ' : 'オン'),
    );
  }
}

状態管理の選択はアプリの規模によって異なります。シンプルなアプリでは setState、中規模のアプリでは Provider、複雑なアプリでは Riverpod、BLoC、GetX などのソリューションを検討できます。

関連情報