【Flutter状態管理入門】StatefulWidget・Provider・Riverpodの違いを徹底解説!
はじめに
こんにちは、株式会社メンバーズCross Applicationのニコラスです。
Flutterの開発では状態管理が大事です。状態管理の手法が多数あり、場合によっては最も適している手法も変わります。
この記事では、Flutterでよく使われるStatefulWidget、Provider、Riverpodの違いについて説明していきたいと思います。
状態管理とは
Flutterにおける状態(state)とは値の変更のあるデータを指しています。
stateは以下の2種類に大別できます。
- Ephemeral State:単一のWidgetでしか使われないstateのこと。UI Stateとも呼ばれる。例:入力フォームのテキストフィールドのstate
- App State:複数のWidgetで共通して使われるstateのこと。Global Stateとも呼ばれる。例:ユーザー認証のstate
StatefulWidget + setState()
StatefulWidgetは外部ライブラリ等を使わずすぐに使える、Flutterにおいて最も単純な状態管理の手法です。小さなアプリや値の変更が少ないWidgetであればStatefulWidgetを使うと良いでしょう。また、Widgetが破棄されたらstateを失うため、単一のwidgetでしか使われていないstateを管理するために適しています。注意点として、setStateを呼び出すたびに全てのWidgetツリーがrebuildされるので余計にsetStateを何回も呼び出してしまうと何回もrebuildがかかってしまい、アプリのパーフォマンスや動作に影響を及ぼす可能性があります。
コードの例は以下の通り
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Counter: $_counter", style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(onPressed: _increment, child: Text("Increment"))
],
),
),
);
}
}
Provider
Providerは複数のWidgetで共通して使われるGlobal Stateを管理するために適している手法です。ProviderはChangeNotifierに基づいて動作しているため、毎回全てのWidgetツリーを更新するのではなく、stateの変化を監視して必要なWidgetのみ更新しています。また、Providerを使うとロジックとUIのコードが区別されるため、コードの役割が明確になり、テストコードも書きやすくなります。
※ChangeNotifierに関してより詳しく知りたい方はこちらの記事をご参考ください!
【Flutter状態管理解説】change_notifier.dartの仕組みと内部実装を学ぶ【How Flutter Works①】
【Flutter状態管理解説】change_notifier.dartの仕組みと内部実装を学ぶ【How Flutter Works②】
コードの例は以下の通り
- 依存関係を追加
dependencies:
provider: version
- Stateクラスを作成
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
- main.dartでProviderを使用
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterProvider(),
child: MyApp(),
),
);
}
- UIでstateを使う
Consumer<CounterProvider>(
builder: (context, provider, child) => Text("Counter: ${provider.count}"),
)
Riverpod
RiverpodはProviderよりさらに進化した状態管理の手法で、複雑なアプリや依存関係の注入が必要なアプリに適しています。そして、Riverpodはプロバイダーの使用がコンパイル時にチェックされるためProviderよりパーフォマンスと安全性が優れています。また、BuildContextに依存せずにstateにアクセスできるメリットもあるためどこからでもstateを参照でき、テストが楽になります。ただし、RiverpodはsetStateとProviderより概念が複雑なため、初めての方にとってはハードルが高いかもしれません。
参考:公式サイト(外部リンク)
コードの例は以下の通り
- 依存関係を追加
dependencies:
flutter_riverpod: version
- State Providerを作成
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateProvider<int>((ref) => 0);
- main.dartでProviderScopeを設定
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
- UIでstateを使う
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text("Counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Counter: $counter"),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text("Increment"),
),
],
),
),
);
}
}
まとめ
状態管理を理解できましたか?状態管理の手法はたくさんありますが、特定の手法が「正解」ということはありません。実際のアプリ開発では、RiverpodとStatfullWidgetを組み合わせる等複数の状態管理手法を組み合わせることもあります。自分が開発しているアプリの要件や規模を考慮して最も適切な状態管理を使いましょう!
この記事を書いた人

Advent Calendar!
Advent Calendar 2024開催中!