Reputation: 3617
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
Reputation: 1531
1.
MyWidget Constructor Error
: There's an error in theconstructor
of theMyWidget
class where super.key is mentioned, which is invalid. To specify constructor arguments, you need to use this.key. Blockquote2.
Loader Visibility Issue
:loader.show()
is placed inside theFutureBuilder
, but it won't always be called. The builder function ofFutureBuilder
doesn't execute on every frame, only when the status of the future changes. If you want the loader to be shown always, moveloader.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 theConsumer<NotificationProvider>
's builder function, but this logic won't execute because the return statement in the builder function is constSizedBox()
, which always returns a SizedBox widget. To display theSnackBar
, 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 thedispose()
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