状態管理を理解する(setState / Provider)
このページで学ぶこと
StatefulWidgetとsetStateで状態を管理できる- 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('リセット'),
),
],
),
),
);
}
}ポイント
_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 さん!'),
],
),
),
);
}
}Provider — 複数ページをまたいだ状態管理
setState は単一のWidget内の状態管理に使います。
複数のページで共有する状態(ログインユーザー・カートの中身など)はProviderで管理できます。
インストール
flutter pub add provider
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();
}
}② アプリ全体に 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(),
),
);
}③ どこからでも状態を使う
// 状態を読むだけ
final count = context.watch<CounterProvider>().count;
// 状態を変更する(再描画は不要)
context.read<CounterProvider>().increment();Riverpod - とんペディアで採用している状態管理
とんペディアアプリでは、provider.dartは使わず、Riverpod を採用しています。
Providerの作者自身が「Riverpodの課題を解決するため」に書いたライブラリで、現在のFlutterではRiverpodがデファクトスタンダードになりつつあります。
とんペディアアプリで実装するときはRiverpodを使いましょう。
インストール
flutter pub add flutter_riverpodネットや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(),
),
);
}Provider の種類
| 書き方 | 外から書き換え | メソッド定義 | 用途 |
|---|---|---|---|
Provider | できない | するように設計されていない | 値を 公開するだけ でよいときの省略記法。定数・他の Provider から計算した値・共有したい依存(API クライアントなど) |
NotifierProvider | できる | 必要 | ユーザー操作などで 状態を書き換える 必要があるとき。カウンター・フォーム・トグルなど UI の状態全般 |
- 状態を書き換える必要がない →
Providerで十分。Notifierクラスを作る必要すらないので短く書けます。
Riverpod版のWidgetクラス
| 素の Flutter | Riverpod 版 | いつ使う |
|---|---|---|
StatelessWidget | ConsumerWidget | Widget 自体には状態を持たず、Provider の値を表示したいだけのとき |
StatefulWidget | ConsumerStatefulWidget | Provider の値に加えて、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;
}
}② Provider として公開する
// lib/src/providers/counter.dart
final counterProvider = NotifierProvider<CounterNotifier, int>(Counter.new);③ ウィジェットから使う: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),
),
);
}
}
ref.watchは、プロバイダの状態を監視し、その状態が変化したときにウィジェットを再描画します。これは、プロバイダの状態をUIに反映させる場合に使われます。ref.readは、プロバイダの状態を一度だけ取得し、その状態を操作するために使います。readを使うと、その状態が変わってもウィジェットの再描画は行われません。
確認しよう
-
StatefulWidgetとsetStateでカウンターを作れた -
TextEditingControllerでテキスト入力を処理できた - Provider をインストールして
ChangeNotifierを作れた - Riverpodをインストールして
NotifierとNotifierProviderを作れた
AIに聞いてみよう
「Flutterの状態管理でRiverpodとProviderはどちらを使うべきですか?初心者に向けた違いを教えてください」