我正在使用 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
1.
:提到super.key的MyWidget Constructor Error
类的constructor
有错误,无效。要指定构造函数参数,您需要使用 this.key。 块引用MyWidget
2.
:Loader Visibility Issue
放置在loader.show()
内部,但并不总是被调用。构建器函数为FutureBuilder
并不在每一帧执行,仅当状态为 未来的变化。如果您希望加载程序始终显示,请移动FutureBuilder
在 FutureBuilder 之外,或者将其放在 setState 内 函数,以便在每一帧上调用它。loader.show()
3.
:SnackBar Logic
的构建器函数中有 SnackBar 逻辑,但是这个逻辑 不会执行,因为构建器函数中的 return 语句是 constConsumer<NotificationProvider>
,它总是返回一个 SizedBox 小部件。显示SizedBox()
,从构建器函数中删除 return 语句 并直接将 SnackBar 逻辑添加到 Stack 的子级中。SnackBar
4.
用于显示 SnackBar。虽然这种方法是正确的,但有一个 未删除回调的潜在问题。这可能会导致 内存泄漏。要删除回调,请使用 StatefulWidget 并删除Memory Leak: WidgetsBinding.instance.addPostFrameCallback
方法中的回调。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();
},
),
],
),
);
}
}