🥳

Riverpodで安全なLoadingダイアログ🐶

2022/10/10に公開

https://zenn.dev/zudah228/articles/fe0fc434832849

この記事を読んでみて、僕も最近全く同じことを考えていたので、面白いなと思ってこの記事を書いてみました。
ボタンを押した時など、Future型の関数を実行して、関数を実行中は、ユーザーに他のボタンを押されたりしたくないので、処理中であることを伝えるダイアログを表示したい場合ってありますよね?
そんな時に僕が書いたコードがあるので、載せてみます💻

あらかじめ用意しておくもの↓


// おなじみのインジケーター
class CenteredIndicator extends StatelessWidget {
  const CenteredIndicator({super.key});

  
  Widget build(BuildContext context) {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }
}

// Stackの中にWidgetとロード中に表示するダイアログを入れて、
// isLoadingProviderがtrueの場合はWidgetを覆い隠すように表示する
// (ネーミングセンスが無いけど無視してください。)
class WidgetWithLoading extends ConsumerWidget {
  const WidgetWithLoading({super.key, required this.child});

  final Widget child;
  
  Widget build(BuildContext context, WidgetRef ref) {
    return Stack(
      children: [
        child,
        if (ref.watch(isLoadingProvider)) const LoadingDialog(),
      ],
    );
  }
}

final isLoadingProvider =
    StateNotifierProvider.autoDispose<IsLoadingController, bool>(
  (ref) => IsLoadingController(),
);

class IsLoadingController extends StateNotifier<bool> {
  IsLoadingController() : super(false);

// Future型の関数を実行中はLoadingDialogを表示する
// Future<T>にすることで色々な関数で利用できる
  Future<T> guardFuture<T>(Future<T> Function() future) async {
    state = true;
    final result = await future();
    state = false;
    return result;
  }
}

// Load中に表示するダイアログ
// Scaffoldで覆うことで、画面全体を隠せるので誤タップを防げる
class LoadingDialog extends StatelessWidget {
  const LoadingDialog({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent, // ダイアログの部分だけ色入れたいので
      body: Center(
        child: Container(
          alignment: Alignment.center,
          width: 200,
          height: 200,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(12),
            color: Theme.of(context).colorScheme.surface,
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              CircularProgressIndicator(),
              Gap(20),
              Text('...Processing'),
            ],
          ),
        ),
      ),
    );
  }
}

実際に使用するときはこんな感じです。
ダイアログを表示したい部分のWidgetを覆ってあげて、StateNotifierで定義したメソッドで処理するだけです🐶

  return WidgetWithLoading( // ここです
      child: Scaffold(
        body: SingleChildScrollView(
          child: Column(
            children: [
	     SomeWidget(),
              ElevatedButton(
                onPressed: () async {
		// ここで実行したいFuture型の関数を渡します
                  final result =
                      await ref.read(isLoadingProvider.notifier).guardFuture<bool>(
                            () async => someFuture(),
                          );

                  if (mounted && result) {
                    Navigator.of(context).pop();
                  }
                },
                child: const Text('保存'),
              ),
            ],
          ),
        ),
      ),
    );

誰かの参考になれば幸いでっす!読んでいただきありがとうございます!!

Discussion