最近はFlutterの状態管理でRiverpodを使用しています。 その中でProviderScopeのoverrideが便利なので使い所を解説します。
この記事では、riverpod 1.0.0-dev.7
を使用しています。
Riverpodを商品詳細のようなIDで内容が変わるページで使用した際、providerにIDを渡す必要があります。その際、よくあるパターンとしてProviderのFamilyを使用して以下の様に対応します。
detailPageProviderFamily(id)
特に以下のサンプルのような、詳細画面から詳細画面にpush遷移できるような場合では、Familyをほぼ必ず使用する必要があります。(Familyを使用しないと元の画面のproviderが引き継がれ、元の画面の状態にも影響を及ぼしてしまうため)
class DetailPage extends StatelessWidget {
const DetailPage({Key? key, required this.id}) : super(key: key);
final int id;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('詳細ページ')),
body: Column(children: [
Consumer(builder: (context, ref, child) {
final name = ref.watch(detailPageProviderFamily(id).select((s) => s.name));
return Text(name, style: TextStyle(fontSize: 30));
}),
Consumer(builder: (context, ref, child) {
final description = ref.watch(detailPageProviderFamily(id).select((s) => s.description));
return Text(description, style: TextStyle(fontSize: 14));
}),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
for (var i = 1; i <= 5; i++)
ElevatedButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => DetailPage(id: i),
));
}, child: Text('商品${i}'))
]),
Consumer(builder: (context, ref, child) {
return Container(
margin: EdgeInsets.only(top: 100),
width: 200,
child: ElevatedButton(
onPressed: ref.read(detailPageProviderFamily(id).notifier).onPurchase,
child: Text('購入'),
),
);
}),
]),
);
}
}
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod/riverpod.dart';
import 'package:state_notifier/state_notifier.dart';
part 'detail_page_controller.freezed.dart';
class DetailPageState with _$DetailPageState {
const factory DetailPageState({
('') String name,
('') String description,
}) = _DetailPageState;
}
final detailPageProviderFamily = StateNotifierProvider.family
.autoDispose<DetailPageController, DetailPageState, int>(
(ref, id) {
return DetailPageController(id: id);
});
class DetailPageController extends StateNotifier<DetailPageState> {
DetailPageController({required int id})
: _id = id,
super(const DetailPageState()) {
_init();
}
final int _id;
Future<void> _init() async {
state = state.copyWith(name: 'ID$_idの商品名', description: 'ID$_idの商品説明');
}
Future<void> onPurchase() async {
// 商品の購入
}
}
この実装をした時、Controller側を呼び出す度に、Providerに毎回IDを渡す必要が出てきて、非常に大変な実装となり不便です...
ここで、ProviderScopeのoverrideを使うと以下の様になります。
class DetailPage extends StatelessWidget {
const DetailPage({Key? key, required this.id}) : super(key: key);
final int id;
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
detailPageProvider.overrideWithProvider(detailPageProviderFamily(id))
],
child: Scaffold(
appBar: AppBar(title: Text('詳細ページ')),
body: Column(children: [
Consumer(builder: (context, ref, child) {
final name = ref.watch(detailPageProvider.select((s) => s.name));
return Text(name, style: TextStyle(fontSize: 30));
}),
Consumer(builder: (context, ref, child) {
final description = ref.watch(detailPageProvider.select((s) => s.description));
return Text(description, style: TextStyle(fontSize: 14));
}),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
for (var i = 1; i <= 5; i++)
ElevatedButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => DetailPage(id: i),
));
}, child: Text('商品${i}'))
]),
Consumer(builder: (context, ref, child) {
return Container(
margin: EdgeInsets.only(top: 100),
width: 200,
child: ElevatedButton(
onPressed: ref.read(detailPageProvider.notifier).onPurchase,
child: Text('購入'),
),
);
}),
]),
),
);
}
}
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod/riverpod.dart';
import 'package:state_notifier/state_notifier.dart';
part 'detail_page_controller.freezed.dart';
class DetailPageState with _$DetailPageState {
const factory DetailPageState({
('') String name,
('') String description,
}) = _DetailPageState;
}
final detailPageProvider =
StateNotifierProvider.autoDispose<DetailPageController, DetailPageState>(
(ref) => throw UnimplementedError());
final detailPageProviderFamily = StateNotifierProvider.family
.autoDispose<DetailPageController, DetailPageState, int>(
(ref, id) {
return DetailPageController(id: id);
});
class DetailPageController extends StateNotifier<DetailPageState> {
DetailPageController({required int id})
: _id = id,
super(const DetailPageState()) {
_init();
}
final int _id;
Future<void> _init() async {
state = state.copyWith(name: 'ID$_idの商品名', description: 'ID$_idの商品説明');
}
void onPurchase() {
// 商品の購入
}
}
ProviderScopeのoverrideを使用することで、それ以下のWidgetではFamilyにIDを渡して呼び出す必要が無くなり、detailPageProviderの形で呼び出せるようになりました。
Riverpodがバージョン1になり、進化した部分の一つとして、ProviderScopeが非常に便利になりました。
今回のサンプルではシンプルな画面なのでメリットが少なく感じますが、画面のコンテンツや状態やアクションが増えたとき、毎回IDを渡すというような実装は非常に苦しい実装になってしまいます。
Riverpodを使用する際はぜひこちらの記事を参考にしてみてください!
可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More