Valerio
Valerio

Reputation: 3617

Flutter's FutureBuilder with methods within causing error

I'm using Flutter with the Provider plugin and I occurred in a error I cannot help myself out.

Inside my main.dart I have this "container"

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();
          },
        ),
      ],
    ),
  );
}

and mywidget is:

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();
          },
        );
      },
    );
  }
}

The error I receive, every time the code hit loader.show() or notification.addException(snapshot.error!) is

 ══╡ 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>

Now, I can understand the error, but how to resolve it? Shouldn't the LoaderProvider and NotificationProvider be "isolated" inside the consumer, so the MyWidget should not render every time?

I'm using Flutter 3.19.6 and Dart 3.3.4

Upvotes: 0

Views: 129

Answers (1)

Devraj
Devraj

Reputation: 1531

1.MyWidget Constructor Error: There's an error in the constructor of the MyWidget class where super.key is mentioned, which is invalid. To specify constructor arguments, you need to use this.key. Blockquote

2.Loader Visibility Issue: loader.show() is placed inside the FutureBuilder, but it won't always be called. The builder function of FutureBuilder doesn't execute on every frame, only when the status of the future changes. If you want the loader to be shown always, move loader.show() outside the FutureBuilder, or put it inside a setState function so that it's called on every frame.

3.SnackBar Logic: There's SnackBar logic inside the Consumer<NotificationProvider>'s builder function, but this logic won't execute because the return statement in the builder function is const SizedBox(), which always returns a SizedBox widget. To display the SnackBar, remove the return statement from the builder function and directly add the SnackBar logic to the Stack's children.

4.Memory Leak: WidgetsBinding.instance.addPostFrameCallback is used to display the SnackBar. While this approach is correct, there's a potential issue where the callback isn't removed. This can lead to memory leaks. To remove the callback, use a StatefulWidget and remove the callback in the dispose() method.

You can follow the structure like this:-

    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();
                },
              ),
            ],
          ),
        );
      }
    }

Upvotes: -1

Related Questions