1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Flutter】Riverpodを使ってダイアログを表示するときの注意点

Posted at

はじめに

小ネタ的な記事となりますが、Riverpodでwatchを使ってダイアログを表示するとエラーになったので回避策について書こうと思います。

エラーになったコード

// お知らせデータ一覧を取得するFutureProvider
final notificationsProvider = FutureProvider<List<String>>(
  (ref) async {
    await Future.delayed(
      const Duration(
        seconds: 1,
      ),
    );

    // エラーになったとする
    if (true) {
      throw Exception("notifications error");
    }

    return [
      "notification1",
      "notification2",
    ];
  },
);

class ErrorHandlingPage extends ConsumerWidget {
  const ErrorHandlingPage({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // FutureProviderをwatch
    final notifications = ref.watch(notificationsProvider);

    // エラーがあったら、ダイアログを表示
    if (notifications.hasError) {
      showAdaptiveDialog(
        context: context,
        builder: (dialogContext) {
          return AlertDialog.adaptive(
            title: const Text("Error"),
            content: Text(notifications.error.toString()),
            actions: [
              TextButton(
                child: const Text("OK"),
                onPressed: () {
                  Navigator.of(dialogContext).pop();
                },
              ),
            ],
          );
        },
      );
    }

    // 以下でnotificationsを参照して、画面作成(省略)
    return Scaffold(;
.......続く

上記のように、watchした値でエラー判定してダイアログを出すと、setState() or markNeedsBuild() called during build.というエラーになります。

回避策1

上記のエラーになった原因ですが、ウィジェットのビルドフェーズ中に新しいダイアログを表示しようとしていることが原因です。
回避するには以下のようにWidgetsBinding.instance.addPostFrameCallbackで囲むといいです。

  // ダイアログ表示部分を囲むと、build後に実行される 
  WidgetsBinding.instance.addPostFrameCallback((_) {
      showAdaptiveDialog(
        context: context,
        builder: (dialogContext) {
          return AlertDialog.adaptive(
            title: const Text("Error"),
            content: Text(notifications.error.toString()),
            actions: [
              TextButton(
                child: const Text("OK"),
                onPressed: () {
                  Navigator.of(dialogContext).pop();
                },
              ),
            ],
          );
        },
      );
    });

回避策2

もう一つの解決方法としてlistenを使う手もあります。

  @override
  Widget build(BuildContext context, WidgetRef ref) {
-    final notifications = ref.watch(notificationsProvider);
-    if (notifications.hasError) {
-      showAdaptiveDialog(
-        context: context,
-        builder: (dialogContext) {
-          return AlertDialog.adaptive(
-            title: const Text("Error"),
-            content: Text(notifications.error.toString()),
-            actions: [
-              TextButton(
-                child: const Text("OK"),
-                onPressed: () {
-                  Navigator.of(dialogContext).pop();
-                },
-              ),
-            ],
-          );
-        },
-      );
-    }
+    ref.listen(notificationsProvider, (previous, next) {
+      if (previous == next) {
+        return;
+      }
+
+      if (next.hasError) {
+        showAdaptiveDialog(
+          context: context,
+          builder: (dialogContext) {
+            return AlertDialog.adaptive(
+              title: const Text("Error"),
+              content: Text(next.error.toString()),
+              actions: [
+                TextButton(
+                  child: const Text("OK"),
+                  onPressed: () {
+                    Navigator.of(dialogContext).pop();
+                  },
+                ),
+              ],
+            );
+          },
+        );
+      }
+    });    

    return Scaffold();
  }

ref.listenを使用すると、指定したプロバイダの状態が変更されたときにコールバックが呼び出されます。このコールバックは、ウィジェットのbuildメソッドの外で実行されるため、buildメソッドの実行中にウィジェットの状態を変更することはありません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?