🆕 2025年11月最新版!
Riverpod 3.0対応、エラー10選追加、パフォーマンス最適化を反映。
5分でわかる:Flutter状態管理完全ガイド
Flutterで状態管理に悩んでいる。そんなあなたのために、状態管理の全てを実践的に解説します。
なぜこのガイドが必要なのか
| 課題 | よくある失敗 | このガイドの解決策 |
|---|---|---|
| どれを選ぶか | 選択基準が不明 | 5手法の詳細比較 |
| 実装方法 | コード例が少ない | 実装コード完全網羅 |
| エラー対応 | トラブル解決できない | エラー10選と解決策 |
| パフォーマンス | アプリが重い | 最適化テクニック |
本記事で学べること
- 基礎理解(第1章):状態管理の必要性
- 手法比較(第2章):5手法の詳細比較
- 実装ガイド(第3-5章):Riverpod、Provider、BLoC
- トラブル解決(第6章):よくあるエラー10選
- 最適化(第7章):パフォーマンスチューニング
ベストマッチ
最短で課題解決する一冊
この記事の内容と高い親和性が確認できたベストマッチです。早めにチェックしておきましょう。
第1章:状態管理完全理解
1.1 状態管理とは
Flutterの状態:
├── ローカル状態(Widget内)
│ └── StatefulWidget + setState()
│
└── グローバル状態(アプリ全体)
├── Provider
├── Riverpod
├── BLoC
├── GetX
└── Redux/MobX等
状態管理が必要な理由:
✅ UI とロジックの分離
✅ 状態の共有と同期
✅ テスト容易性
✅ パフォーマンス最適化
✅ 保守性の向上1.2 StatefulWidgetの限界
// ❌ 問題:状態を子に渡すのが複雑
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int count = 0;
void increment() {
setState(() { count++; });
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ChildA(count: count),
ChildB(count: count),
ChildC(onPressed: increment), // コールバック地獄
],
);
}
}
// ✅ 解決:状態管理ライブラリ使用
final counterProvider = StateProvider((ref) => 0);
class ParentWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
children: [
ChildA(), // 各自でcounterProvider参照
ChildB(),
ChildC(),
],
);
}
}さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
第2章:5大手法徹底比較
2.1 総合比較表
| 項目 | Riverpod | Provider | BLoC | GetX | Redux |
|---|---|---|---|---|---|
| 推奨度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| 学習曲線 | 中 | 低 | 高 | 低 | 高 |
| 型安全性 | ◎ | ○ | ◎ | △ | ○ |
| テスト容易性 | ◎ | ○ | ◎ | △ | ◎ |
| ボイラープレート | 少 | 少 | 多 | 最少 | 最多 |
| パフォーマンス | ◎ | ○ | ◎ | ◎ | ○ |
| コミュニティ | 大 | 大 | 大 | 中 | 中 |
| メンテナンス | 活発 | 緩やか | 活発 | 活発 | 緩やか |
2.2 プロジェクト規模別推奨
小規模(1-5画面):
推奨: Provider または Riverpod
理由:
- セットアップが簡単
- 学習コスト最小
- 十分な機能
中規模(5-20画面):
推奨: Riverpod
理由:
- 型安全性
- テスト容易
- スケーラビリティ
大規模(20+画面):
推奨: Riverpod または BLoC
理由:
- 厳格なアーキテクチャ
- チーム開発対応
- 複雑な状態管理
エンタープライズ:
推奨: BLoC + Riverpod
理由:
- 明確な責務分離
- テスト完備
- 長期保守性2.3 選択フローチャート
Q1. チームの経験は?
│
├─ Flutter初心者多い
│ └─ [Provider] または [Riverpod]
│
├─ Flutter経験者多い
│ └─ Q2へ
│
└─ 厳格なアーキテクチャ必要
└─ [BLoC]
Q2. プロジェクト規模は?
│
├─ 小規模・個人開発
│ └─ [GetX] または [Riverpod]
│
├─ 中規模
│ └─ [Riverpod] 推奨
│
└─ 大規模
└─ [Riverpod] または [BLoC]
Q3. 開発速度重視?
│
├─ Yes → [GetX](独自色強い)
│
└─ No → [Riverpod](標準的)さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
第3章:Riverpod完全ガイド(2025年推奨)
3.1 基本セットアップ
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
dev_dependencies:
riverpod_generator: ^2.4.0
build_runner: ^2.4.03.2 実装パターン集
// パターン1:StateProvider(シンプルな状態)
final counterProvider = StateProvider((ref) => 0);
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('$count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
// パターン2:StateNotifierProvider(複雑な状態)
class TodoList extends StateNotifier<List<Todo>> {
TodoList() : super([]);
void add(String title) {
state = [...state, Todo(id: DateTime.now().toString(), title: title)];
}
void toggle(String id) {
state = [
for (final todo in state)
if (todo.id == id)
todo.copyWith(completed: !todo.completed)
else
todo,
];
}
void remove(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
final todoListProvider = StateNotifierProvider<TodoList, List<Todo>>((ref) {
return TodoList();
});
// パターン3:FutureProvider(非同期データ)
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
final repository = ref.watch(userRepositoryProvider);
return await repository.getUser(userId);
});
// 使用例
Consumer(
builder: (context, ref, child) {
final userAsync = ref.watch(userProvider('123'));
return userAsync.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
},
)
// パターン4:StreamProvider(リアルタイム)
final messagesProvider = StreamProvider.autoDispose<List<Message>>((ref) {
final firestore = FirebaseFirestore.instance;
return firestore.collection('messages').snapshots().map(
(snapshot) => snapshot.docs.map((doc) => Message.fromJson(doc.data())).toList(),
);
});
// パターン5:Provider依存(他のProviderに依存)
final filteredTodosProvider = Provider<List<Todo>>((ref) {
final todos = ref.watch(todoListProvider);
final filter = ref.watch(filterProvider);
switch (filter) {
case Filter.all:
return todos;
case Filter.completed:
return todos.where((todo) => todo.completed).toList();
case Filter.active:
return todos.where((todo) => !todo.completed).toList();
}
});3.3 Riverpod 3.0 新機能
// Code Generation(型安全性向上)
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
// 使用例
final counter = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
第4章:Provider実装ガイド
4.1 基本実装
// ChangeNotifierProvider
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
List<Item> get items => _items;
int get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
void add(Item item) {
_items.add(item);
notifyListeners();
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
}
// 使用例
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = context.watch<CartModel>();
return ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: Icon(Icons.remove),
onPressed: () => cart.remove(item),
),
);
},
);
}
}さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
第5章:BLoC実装ガイド
5.1 基本セットアップ
// Event
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// State
class CounterState {
final int count;
CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<Increment>((event, emit) {
emit(CounterState(state.count + 1));
});
on<Decrement>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
// 使用例
BlocProvider(
create: (context) => CounterBloc(),
child: CounterScreen(),
)
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Column(
children: [
Text('${state.count}'),
Row(
children: [
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Decrement()),
child: Text('-'),
),
],
),
],
);
},
);
}
}さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
第6章:よくあるエラー10選と解決策
エラー1:ProviderNotFoundException
エラー:
Error: Could not find the correct Provider<Counter> above this Widget
原因:
Providerスコープ外でアクセス
解決策:
```dart
// ❌ 間違い
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ここではProviderがまだ存在しない
final counter = context.watch<Counter>(); // エラー!
return ProviderScope(
child: MaterialApp(...),
);
}
}
// ✅ 正しい
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
home: CounterScreen(), // ここでアクセス
),
);
}
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // OK
return Text('$counter');
}
}
### エラー2-10のクイックリファレンス
| エラー | 原因 | 解決策 |
|--------|------|--------|
| **2. setState after dispose** | Widget破棄後に状態更新 | mounted確認、dispose処理 |
| **3. Circular dependency** | Provider循環参照 | 依存関係見直し |
| **4. Memory leak** | disposeし忘れ | autoDispose使用 |
| **5. Provider update loop** | 無限ループ | watch/read使い分け |
| **6. BLoC not closed** | BLoC破棄忘れ | close()呼び出し |
| **7. Stream not canceled** | Stream購読解除忘れ | autoDispose、cancel() |
| **8. Context error** | BuildContext誤用 | Consumer使用 |
| **9. Type mismatch** | 型不一致 | 正しい型指定 |
| **10. Hot reload issue** | 状態がリセット | 開発時のみの問題 |
## 第7章:パフォーマンス最適化
### 7.1 最適化テクニック
```dart
// テクニック1:select使用(部分監視)
// ❌ 非効率:User全体を監視
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider); // User変更で毎回再ビルド
return Text(user.name);
}
// ✅ 効率的:name のみ監視
Widget build(BuildContext context, WidgetRef ref) {
final name = ref.watch(userProvider.select((user) => user.name));
return Text(name); // nameが変わった時だけ再ビルド
}
// テクニック2:autoDispose(自動破棄)
final userProvider = FutureProvider.autoDispose.family<User, String>((ref, id) async {
// 使用されなくなると自動的に破棄される
return await fetchUser(id);
});
// テクニック3:family(キャッシュ活用)
final todoProvider = Provider.family<Todo, String>((ref, id) {
// IDごとにキャッシュされる
return ref.watch(todosProvider).firstWhere((todo) => todo.id == id);
});
// テクニック4:keepAlive(状態保持)
final userProvider = FutureProvider.autoDispose((ref) async {
// 一度取得したら破棄しない
ref.keepAlive();
return await fetchUser();
});
// テクニック5:debounce(検索など)
class SearchNotifier extends StateNotifier<String> {
SearchNotifier() : super('');
Timer? _debounce;
void updateQuery(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
state = query; // 500ms後に更新
});
}
@override
void dispose() {
_debounce?.cancel();
super.dispose();
}
}7.2 メモリ管理
// メモリリーク回避
class MyWidget extends ConsumerStatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends ConsumerState<MyWidget> {
late final StreamSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = someStream.listen((data) {
// 処理
});
}
@override
void dispose() {
_subscription.cancel(); // 必ず解除
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
まとめ:最適な状態管理手法の選択
Flutter状態管理は、プロジェクトの特性に合わせた選択が重要です。
✅ 選択基準まとめ
2025年推奨ランキング:
1位: Riverpod
✅ 型安全性最高
✅ テスト容易
✅ 学習コスト中程度
✅ 将来性高い
2位: BLoC
✅ 厳格なアーキテクチャ
✅ 大規模向け
✅ テスト完備
3位: Provider
✅ シンプル
✅ 公式推奨(過去)
✅ 小規模向け
4位: GetX
✅ 開発速度最速
✅ 個人開発向け
✅ 独自色強い
避けるべき:
❌ 素のStatefulWidget(大規模)
❌ Redux(ボイラープレート過多)今すぐRiverpodで型安全な状態管理を始めよう。
※本記事の情報は2025年11月時点のものです。最新情報は各パッケージの公式ドキュメントでご確認ください。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
この記事をシェア




![Ansible実践ガイド 第4版[基礎編] impress top gearシリーズ](https://m.media-amazon.com/images/I/516W+QJKg1L._SL500_.jpg)
