Skip to Content
ドキュメントFlutterコース状態管理を理解する

状態管理を理解する(setState / Provider)

このページで学ぶこと

  • StatefulWidgetsetState で状態を管理できる
  • Provider パッケージで複数ページをまたいだ状態管理ができる

StatefulWidget と setState

前のページで StatelessWidget を学びました。
ユーザーの操作に反応してUIを変えたいときは StatefulWidget を使います。

カウンターを作る

import 'package:flutter/material.dart';
 
void main() => runApp(const MyApp());
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: CounterPage());
  }
}
 
// StatefulWidget のテンプレート
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});
 
  @override
  State<CounterPage> createState() => _CounterPageState();
}
 
// 状態(State)を持つクラス
class _CounterPageState extends State<CounterPage> {
  int _count = 0;  // 状態変数(_ は プライベートを表す慣習)
 
  void _increment() {
    setState(() {
      // setState() の中で状態を変更すると、buildが再実行される
      _count++;
    });
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('カウンター')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_count', style: const TextStyle(fontSize: 64)),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: _increment,
              child: const Text('+1'),
            ),
            TextButton(
              onPressed: () => setState(() => _count = 0),
              child: const Text('リセット'),
            ),
          ],
        ),
      ),
    );
  }
}
dart

ポイント

  • _count などのアンダースコアから始まる変数はプライベート(Dartの慣習)
  • setState() を呼ばないと画面が更新されない
  • setState() の中で状態を変更する

フォームを作る

class FormPage extends StatefulWidget {
  const FormPage({super.key});
 
  @override
  State<FormPage> createState() => _FormPageState();
}
 
class _FormPageState extends State<FormPage> {
  // TextEditingController でテキストフィールドの値を管理する
  final _nameController = TextEditingController();
  String _submitted = '';
 
  @override
  void dispose() {
    // メモリリークを防ぐため、不要になったら破棄する
    _nameController.dispose();
    super.dispose();
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('フォーム')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(
                labelText: '名前',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _submitted = _nameController.text;
                });
              },
              child: const Text('送信'),
            ),
            if (_submitted.isNotEmpty)
              Text('こんにちは、$_submitted さん!'),
          ],
        ),
      ),
    );
  }
}
dart

Provider — 複数ページをまたいだ状態管理

setState は単一のWidget内の状態管理に使います。
複数のページで共有する状態(ログインユーザー・カートの中身など)はProviderで管理できます。

インストール

flutter pub add provider
bash

flutter pub add は npm install に相当するコマンドです。

Provider の基本的な使い方の例

① 状態を管理するクラスを作る

// lib/providers/counter_provider.dart
import 'package:flutter/foundation.dart';
 
class CounterProvider extends ChangeNotifier {
  int _count = 0;
 
  int get count => _count;  // ゲッター
 
  void increment() {
    _count++;
    notifyListeners();  // 変更を通知 → 関連するWidgetが再描画される
  }
 
  void reset() {
    _count = 0;
    notifyListeners();
  }
}
dart

② アプリ全体に Provider を提供する

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/counter_provider.dart';
 
void main() {
  runApp(
    // ChangeNotifierProvider でアプリ全体を囲む
    ChangeNotifierProvider(
      create: (_) => CounterProvider(),
      child: const MyApp(),
    ),
  );
}
dart

③ どこからでも状態を使う

// 状態を読むだけ
final count = context.watch<CounterProvider>().count;
 
// 状態を変更する(再描画は不要)
context.read<CounterProvider>().increment();
dart

Riverpod - とんペディアで採用している状態管理

とんペディアアプリでは、provider.dartは使わず、Riverpod を採用しています。 Providerの作者自身が「Riverpodの課題を解決するため」に書いたライブラリで、現在のFlutterではRiverpodがデファクトスタンダードになりつつあります。

とんペディアアプリで実装するときはRiverpodを使いましょう。

インストール

flutter pub add flutter_riverpod
bash

ネットやAIの出力で見かける@riverpodアノテーションを使うコード生成スタイル(riverpod_generator)もありますが、 とんペディアのアプリでは**使っていません。**ここでは手書きで宣言するスタイルを紹介します。

アプリ全体をProviderScopeで囲む

runAppの一番外側をProviderScopeで包みます。これによってアプリのどこからでもProviderが使えるようになります.

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod_flutter_riverpod.dart';
 
void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
dart

Provider の種類

書き方外から書き換えメソッド定義用途
Providerできないするように設計されていない値を 公開するだけ でよいときの省略記法。定数・他の Provider から計算した値・共有したい依存(API クライアントなど)
NotifierProviderできる必要ユーザー操作などで 状態を書き換える 必要があるとき。カウンター・フォーム・トグルなど UI の状態全般
  • 状態を書き換える必要がないProvider で十分。Notifier クラスを作る必要すらないので短く書けます。

Riverpod版のWidgetクラス

素の FlutterRiverpod 版いつ使う
StatelessWidgetConsumerWidgetWidget 自体には状態を持たず、Provider の値を表示したいだけのとき
StatefulWidgetConsumerStatefulWidgetProvider の値に加えて、initState / dispose などウィジェット固有のライフサイクルが必要なとき

Provider の基本的な使い方の例

① 状態を管理するクラスを作る

// lib/src/providers/counter.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
 
class CounterNotifier extends Notifier<int> {
  @override
  int build() {
    // Providerが初めて読まれたときに呼ばれる
    return 0;
  }
 
  void increment() {
    state++;
  }
 
  void reset() {
    state = 0;
  }
}
dart

② Provider として公開する

// lib/src/providers/counter.dart
final counterProvider = NotifierProvider<CounterNotifier, int>(Counter.new);
dart

③ ウィジェットから使う:ConsumerWidget

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_app/src/providers/counter.dart';
 
class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
  // 値を監視する(変わったら build が再実行される)
    final count = ref.watch(counterProvider);
 
    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod カウンター')),
      body: Center(
        child: Text('$count', style: const TextStyle(fontSize: 64)),
      ),
      floatingActionButton: FloatingActionButton(
        // 値を変更する(再描画は不要なので read を使う)
        /
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}
dart

ref.watchは、プロバイダの状態を監視し、その状態が変化したときにウィジェットを再描画します。これは、プロバイダの状態をUIに反映させる場合に使われます。 ref.readは、プロバイダの状態を一度だけ取得し、その状態を操作するために使います。readを使うと、その状態が変わってもウィジェットの再描画は行われません。


確認しよう

  • StatefulWidgetsetState でカウンターを作れた
  • TextEditingController でテキスト入力を処理できた
  • Provider をインストールして ChangeNotifier を作れた
  • RiverpodをインストールしてNotifierNotifierProviderを作れた

AIに聞いてみよう

「Flutterの状態管理でRiverpodとProviderはどちらを使うべきですか?初心者に向けた違いを教えてください」


次のステップ

画面遷移を実装しよう

Last updated on