【Flutter】個人的Riverpodの理解まとめ
~Riverpodとは何か~
Flutterアプリケーションの状態管理を行うためのパッケージ。
共通の設定
⭐ProviderScopeの追加
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
~各Widgetについて~
Consumer
⭐️Consumer内でrefが参照できる。
⭐️影響範囲が小さい。
Consumer(builder: (context, ref, child) {
return Text(
'${ref.watch(_hitsujiCounterProvider)}',
);
}),
ConsumerWidget
⭐️buildメソッド内でrefが参照できる。
⭐️Riverpod版のStatelessWidget。
class MyHomePage extends ConsumerWidget {
MyHomePage({super.key});
final _hitsujiCounterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
...
}
}
ConsumerStatefulWidget
⭐️State内からrefが参照できる。
⭐️Riverpod版のStatefulWidget。
class MyHomePage extends ConsumerStatefulWidget {
ConsumerState<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends ConsumerState<MyHomePage> {
final _hitsujiCounterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () =>
ref.read(_hitsujiCounterProvider.notifier).update((state) => state + 1),
child: const Icon(Icons.add),
),
);
}
}
~各Providerについて~
全部で6種類あり、管理する対象や更新方法に合わせて適切なPeoviderを選べる。
・Provider
・StateProvider
・StateNotifierProvider
・FutureProvider
・StreamProvider
・ChangeNotifierProvider
Provider
⭐️一度定義すると状態が外部から変更できない。(定数チック)
⭐️初期化時点で他のProviderの値を参照して、その値を加工した値をProviderの値として保持することも可能。
⭐️グローバルな値を提供するために使われることが多い。
定義
final _hitsujiCounterProvider = ChangeNotifierProvider((ref) => HitsujiCounter());
late final _doubleProvider =
Provider<int>((ref) => ref.watch(_hitsujiCounterProvider).hitsujiCounter * 2);
参照
Text(
'${ref.watch(_doubleProvider)}',
),
StateProvider
⭐️Providerのnotifierを参照し、直接値を変更する。(変数チック)
⭐️イミュータブルな状態を提供。
⭐️値の変更は新しい値を生成することによって行われる。
定義
final animalTypeProvider =
StateProvider<AnimalType>((ref) => AnimalType.neko);
参照
final animalTypeNotifier = ref.watch(animalTypeProvider.notifier);
更新
・update : 今の値を加工した結果を設定したいときに使用する。
・state : 今の値が必要なときに使用する。
// updateでの更新の例)
animalTypeNotifier.update((state) => AnimalType.inu);
// stateでの更新の例)
animalTypeNotifier.state = AnimalType.ushi;
StateNotifierProvider
⭐️状態を表す変数(state)と、状態を更新するメソッドを持つProvider。
⭐️StateNotifierを継承したクラスを監視して、状態に変化があれば画面を更新する。
⭐️高度な状態管理に向いている。
定義
final animalTypeNotifierProvider = StateNotifierProvider<
animalTypeNotifier, animalFoodTypeState>(
(ref) => animalTypeNotifier(),
);
class animalTypeNotifier
extends StateNotifier<animalFoodTypeState> {
animalTypeNotifier()
: super(const animalFoodTypeState());
void checkFavoriteFoodType(AnimalFoodType value) {
state = state.copyWith(animalFoodType: value);
}
}
更新
final animalFoodTypeStateNotifier =
ref.watch(animalTypeNotifierProvider.notifier);
/// 呼び出し
animalFoodTypeStateNotifier
.checkFavoriteFoodType(animalFoodType);
参照
final animalFoodTypeState = ref.watch(animalTypeNotifierProvider);
/// 呼び出し
Text(animalFoodTypeState.flavor);
ChangeNotifierProvider
⭐️ミュータブルなクラスの状態管理に使う。
⭐️Riverpodの前身のパッケージ「Provider」との互換のためにあるProviderで、パッケージの作者としては非推奨。
定義
class HitsujiCounter extends ChangeNotifier {
int _hitsujiCounter = 0;
get hitsujiCounter => _hitsujiCounter;
void hitsujiCountUp() {
_hitsujiCounter++;
notifyListeners();
}
}
final _hitsujiCounterProvider = ChangeNotifierProvider((ref) => HitsujiCounter());
参照
Text('${ref.watch(_hitsujiCounterProvider).hitsujiCounter}'),
更新
ref.read(_hitsujiCounterProvider).hitsujiCountUp()
FutureProvider
⭐️Future型を取り扱うProvider。
⭐️非同期データの取得に使用して、データが使用可能(取得完了)になるとWidgetを更新する。
⭐️例としてAPIの呼び出しやSharedPreferencesなどの非同期処理など。
定義
final animalListFetchProvider = FutureProvider((ref) async {
return ref.watch(animalListRepositoryProvider).fetch();
});
参照
final animalListAsync = ref.watch(animalListFetchProvider);
...
animalListAsync.when(
data: (data) {
final animalList = _createNewList(data);
return animalList.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
for (final item in animalList) ...{
_Record(
item: item,
),
},
],
),
)
: const NoDataDisplay();
},
error: (_, __) => const Text('error'),
loading: () => loadingWidget,
),
更新(再取得)
// 非同期データを取得する際に、キャッシュされたデータを破棄して新しいデータを再取得する
ref.invalidate(animalListFetchProvider)
StreamProvider
⭐️Future型を取り扱うProvider。
⭐️リアルタイムにデータを取得し、データがストリーム内で更新されるたびにウィジェットを更新する。
⭐️例としてWebSocketなど。
定義
final chatProvider = StreamProvider<List<String>>((ref) async* {
final socket = await Socket.connect('my-api', 4242);
ref.onDispose(socket.close);
var allMessages = const <String>[];
await for (final message in socket.map(utf8.decode)) {
allMessages = [...allMessages, message];
yield allMessages;
}
});
参照
final liveChats = ref.watch(chatProvider);
...
liveChats.when(
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text(error.toString()),
data: (messages) {
return ListView.builder(
reverse: true,
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return Text(message);
},
);
},
);
値の参照
read
取得時点での状態を取得。
buildメソッド中では使わない。onTapなどのイベント内や、ライフサイクルのイベント(StatefulWidgetのinitStateなど)内で使用する。
watch
継続的に状態を監視する。buildメソッド中で使ってOK!
listen
状態が変更された際に実行するイベントを定義できる。
ref.listen(_stateProvider, (previous, next) {
print('previous: $previous next: $next');
});
Provider修飾子
family
Providerに引数を渡せる。
autoDispose
Providerが参照されなくなったタイミングで状態を破棄する。
ライフサイクルイベント
初期化のタイミング
Providerの定義時ではなく、ConsumerWidgetのbuild内などから実際に呼び出されたときに初期化される。
ref.onDispose
Providerが破棄されるときのイベントを指定する。
ref.onCancel
Providerの最後のリスナーが削除されたときに実行される。
(使ったことないかも。。。)
ref.onResume
Providerの最後のリスナーが削除された後、再度監視が開始されるときに実行される。
(これも使ったことない。。。)
ref.onDispose(() => print('provider was disposed (page=$page)'));
ref.onCancel(() => print('provider was canceled (page=$page)'));
ref.onResume(() => print('provider was resumed (page=$page)'));
終わりに
riverpodをちゃんと理解できていなかったことを後ろめかしく思っていたので、今回色々調べてみて、過去に自分が書いたソースコード見てなんでここHooks使ってないのにHookConsumer使ってるんだろ🙄、、とか、ここで.autoDisposeしてる理由ってなんでだろ、、とか、メンバーが書いたものを見て「ふんふんなるほど」と思えるようになったりした気がします。
Discussion