Flutter 的 FutureBuilder 中的方法导致错误

问题描述 投票:0回答:1

我正在使用 Flutter 和 Provider 插件,但遇到了一个我无法帮助自己的错误。

在我的 main.dart 中我有这个“容器”

Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        MyWiget(),
        Consumer<LoaderProvider>(
          builder: (context, loader, child) {
            return Positioned.fill(
              child: loader.isLoading ? const OverlayLoadingView() : const SizedBox(),
            );
          },
        ),
        Consumer<NotificationProvider>(
          builder: (context, notification, child) {
            if (notification.message.isNotEmpty) {
              WidgetsBinding.instance.addPostFrameCallback((_) {
                ScaffoldMessenger.of(context)
                    .showSnackBar(
                      SnackBar(content: Text(notification.message)),
                    )
                    .closed
                    .then(
                      (_) => notification.remove(),
                    );
              });
            }
            return const SizedBox();
          },
        ),
      ],
    ),
  );
}

我的小部件是:

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer3<HttpProvider, LoaderProvider, NotificationProvider>(
      builder: (context, http, loader, notification, child) {
        return FutureBuilder(
          future: http.get('...'),
          builder: (context, snapshot) {
            loader.show();

            if (snapshot.hasError) {
              notification.addException(snapshot.error!);
            }

            return Container();
          },
        );
      },
    );
  }
}

每次代码点击

loader.show()
notification.addException(snapshot.error!)
时,我收到的错误是

 ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞═════════════════════
The following assertion was thrown while dispatching
notifications for LoaderProvider:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<LoaderProvider?> widget cannot be
marked as needing to build because the framework is already in
the process of building widgets. A widget can be marked as
needing to be built during the build phase only if one of its
ancestors is currently building. This exception is allowed
because the framework builds parent widgets before children,
which means a dirty descendant will always be built. Otherwise,
the framework might not visit this widget during this build
phase.
The widget on which setState() or markNeedsBuild() was called
was:
  _InheritedProviderScope<LoaderProvider?>
The widget which was currently being built when the offending
call was made was:
  FutureBuilder<MyClass>

现在我可以理解这个错误了,但是如何解决呢? LoaderProvider和NotificationProvider不应该在消费者内部“隔离”,这样MyWidget就不应该每次都渲染吗?

我正在使用 Flutter 3.19.6 和 Dart 3.3.4

flutter flutter-provider flutter-futurebuilder
1个回答
0
投票

1.

MyWidget Constructor Error
:提到super.key的
constructor
类的
MyWidget
有错误,无效。要指定构造函数参数,您需要使用 this.key。 块引用

2.

Loader Visibility Issue
loader.show()
放置在
FutureBuilder
内部,但并不总是被调用。构建器函数为
FutureBuilder
并不在每一帧执行,仅当状态为 未来的变化。如果您希望加载程序始终显示,请移动
loader.show()
在 FutureBuilder 之外,或者将其放在 setState 内 函数,以便在每一帧上调用它。

3.

SnackBar Logic
Consumer<NotificationProvider>
的构建器函数中有 SnackBar 逻辑,但是这个逻辑 不会执行,因为构建器函数中的 return 语句是 const
SizedBox()
,它总是返回一个 SizedBox 小部件。显示
SnackBar
,从构建器函数中删除 return 语句 并直接将 SnackBar 逻辑添加到 Stack 的子级中。

4.

Memory Leak: WidgetsBinding.instance.addPostFrameCallback
用于显示 SnackBar。虽然这种方法是正确的,但有一个 未删除回调的潜在问题。这可能会导致 内存泄漏。要删除回调,请使用 StatefulWidget 并删除
dispose()
方法中的回调。

您可以遵循这样的结构:-

    class MyWidget extends StatelessWidget {
  const MyWidget({Key? key});

  @override
  Widget build(BuildContext context) {
    return Consumer3<HttpProvider, LoaderProvider, NotificationProvider>(
      builder: (context, http, loader, notification, child) {
        // Start loading when MyWidget is built
        Future<void>.microtask(() {
          loader.show();
        });

        return FutureBuilder(
          future: http.get('...'),
          builder: (context, snapshot) {
            // Hide loader when future completes
            if (snapshot.connectionState == ConnectionState.done) {
              Future<void>.microtask(() {
                loader.hide();
              });
            }

            if (snapshot.hasError) {
              // Handle error
              notification.addException(snapshot.error!);
            }

            return Container();
          },
        );
      },
    );
  }
}

    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Stack(
            children: [
              MyWidget(),
              Consumer<LoaderProvider>(
                builder: (context, loader, child) {
                  // Show loader when isLoading is true
                  return loader.isLoading ? const OverlayLoadingView() : const SizedBox();
                },
              ),
              Consumer<NotificationProvider>(
                builder: (context, notification, child) {
                  if (notification.message.isNotEmpty) {
                    // Show SnackBar if there's a message
                    WidgetsBinding.instance!.addPostFrameCallback((_) {
                      ScaffoldMessenger.of(context)
                          .showSnackBar(
                            SnackBar(content: Text(notification.message)),
                          )
                          .closed
                          .then(
                            (_) => notification.remove(),
                          );
                    });
                  }
                  return const SizedBox();
                },
              ),
            ],
          ),
        );
      }
    }
© www.soinside.com 2019 - 2024. All rights reserved.