🐥
Riverpod で安全な Loading ダイアログ試作
課題感
こういう実装が多い
class HogePage extends StatelessWidget {
// ~~
onPressed: () async {
showDialog(context: context, (_) => Dialog());
await doAysnc();
// ここ
Navigator.of(context).pop();
}
}
何を pop() してるか分からなかったり(まあコメントつければ良いが)、もし何かのミスでダイアログが閉じられてしまった or 表示されなかった。など変な挙動になってしまう。
というか、変な挙動になりうるの実装なのが嫌だ。
実装内容
ref.listen
を活用して、StateProvider で表示/非表示を管理する。
ref.listen
の一般的な活用法。
ref.listen
: プロバイダの値を監視し、値が変化するたびに呼び出されるコールバック関数(画面遷移、ダイアログの表示など)を登録する。
引用元:https://riverpod.dev/ja/docs/concepts/reading/#ref-を使ってプロバイダを利用する
final _isLoading = StateProvider((_) => false);
/// ローディングダイアログの表示
final showLoadingDialog = Provider<VoidCallback>((ref) {
return () {
ref.read(_isLoading.notifier).update((_) => true);
unawaited(showDialog(
context: ref.read(navigatorKeyProvider).currentContext!,
barrierDismissible: false,
builder: (_) => const LoadingDialog()));
};
});
/// ローディングダイアログの表示
final hideLoadingDialog = Provider<VoidCallback>((ref) {
return () {
// State の更新のみ
ref.read(_isLoading.notifier).update((_) => false);
};
});
class LoadingDialog extends ConsumerStatefulWidget {
const LoadingDialog({Key? key}) : super(key: key);
ConsumerState<ConsumerStatefulWidget> createState() => _LoadingDialogState();
}
class _LoadingDialogState extends ConsumerState<LoadingDialog> {
Widget build(BuildContext context,) {
// state の更新を検知して、pop させる
ref.listen<bool>(_isLoading, ((_, next) {
if (mounted) {
Navigator.pop(context);
}
}));
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 50,
width: 50,
child: CircularProgressIndicator(),
),
const SizedBox(
height: 32,
),
const Text('ローティングなう'),
],
),
contentPadding: const EdgeInsets.all(32),
);
}
}
問題点?
- ダイアログが複数出ていた場合は、変な挙動をするが、そもそもそんな状況はないので、考えなくて良い。
- Riverpod などのグローバルな状態管理パッケージに依存しているが、状態管理を StatefulWidget などのみに依存することはないのでとりあえず大丈夫。
- Stack を活用した実装をしてくれた方がいたので紹介しておく。この方法なら、Widget で囲う必要があるが、複数のダイアログが出てしまった場合などは考えなくて良くなる。
https://zenn.dev/takewoy/articles/7492ffebc62414
- Stack を活用した実装をしてくれた方がいたので紹介しておく。この方法なら、Widget で囲う必要があるが、複数のダイアログが出てしまった場合などは考えなくて良くなる。
利点
- _isLoading の StateProvider はプライベートで、「呼び出す」と「非表示にする」のみを公開しているので安全。
- どういう場合でも「呼び出す」と「非表示にする」を呼び出せば実現できるので、良い感じに隠蔽化されている。
発展
hideLoadingDialog
の機能拡張。ダイアログを非表示にするときに、メッセージを渡すことで、SnackBar
を表示させている。
SnackBar
でなくとも、いろんな拡張できる。
ダイアログを非表示にさせる以上のことをしているので、かならずしもベストな実装とは言えないが、引数は optional だし、「ローディンングが終わった後に、何かしらメッセージを表示する」は一般的な実装なので、個人開発とかではガンガン使う気がする。
/// errorMessage / successMessage を渡すことで、snackBar を表示させられる。
final hideLoadingDialog =
Provider<void Function({String? errorMessage, String? successMessage})>(
(ref) {
return ({String? errorMessage, String? successMessage}) {
ref.read(_isLoading.notifier).update((_) => false);
if (errorMessage != null) {
scaffoldMessengerKey.show(errorMessage,
backgroundColor: Colors.redAccent);
}
if (successMessage != null) {
scaffoldMessengerKey.show(successMessage, backgroundColor: Colors.blue);
}
};
});
Discussion
これするなら Navigator.pop() でええやんとなっております