Yan*_*n39 22 dart flutter flutter-provider
我想加载事件列表并在获取数据时显示加载指示器。
我正在尝试提供者模式(实际上是重构一个现有的应用程序)。
因此,事件列表的显示取决于提供程序中管理的状态。
问题是当我打电话notifyListeners()太快时,我得到这个异常:
????????? 基础库捕获的异常 ????????
为 EventProvider 分派通知时抛出以下断言:
在构建期间调用 setState() 或 markNeedsBuild() 。
...
EventProvider 发送通知是:“EventProvider”的实例
??????????????????????????????????????????
在调用notifyListeners()解决问题之前等待几毫秒(请参阅下面提供程序类中的注释行)。
这是一个基于我的代码的简单示例(希望不要过于简化):
主要功能:
Future<void> main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LoginProvider()),
ChangeNotifierProvider(create: (_) => EventProvider()),
],
child: MyApp(),
),
);
}
Run Code Online (Sandbox Code Playgroud)
根小部件:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final LoginProvider _loginProvider = Provider.of<LoginProvider>(context, listen: true);
final EventProvider _eventProvider = Provider.of<EventProvider>(context, listen: false);
// load user events when user is logged
if (_loginProvider.loggedUser != null) {
_eventProvider.fetchEvents(_loginProvider.loggedUser.email);
}
return MaterialApp(
home: switch (_loginProvider.status) {
case AuthStatus.Unauthenticated:
return MyLoginPage();
case AuthStatus.Authenticated:
return MyHomePage();
},
);
}
}
Run Code Online (Sandbox Code Playgroud)
主页:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final EventProvider _eventProvider = Provider.of<EventProvider>(context, listen: true);
return Scaffold(
body: _eventProvider.status == EventLoadingStatus.Loading ? CircularProgressIndicator() : ListView.builder(...)
)
}
}
Run Code Online (Sandbox Code Playgroud)
事件提供者:
enum EventLoadingStatus { NotLoaded, Loading, Loaded }
class EventProvider extends ChangeNotifier {
final List<Event> _events = [];
EventLoadingStatus _eventLoadingStatus = EventLoadingStatus.NotLoaded;
EventLoadingStatus get status => _eventLoadingStatus;
Future<void> fetchEvents(String email) async {
//await Future.delayed(const Duration(milliseconds: 100), (){});
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
}
Run Code Online (Sandbox Code Playgroud)
有人可以解释会发生什么吗?
Abi*_*n47 45
您正在fetchEvents从根小部件的构建代码中调用。在 中fetchEvents,您调用notifyListeners,其中,除其他外,调用setState正在侦听事件提供程序的小部件。这是一个问题,因为setState当小部件处于重建过程中时,您无法调用小部件。
现在,您可能会想“但是该fetchEvents方法被标记为async应该异步运行以备后用”。答案是“是也不是”。asyncDart 的工作方式是,当你调用一个async方法时,Dart 尝试同步运行尽可能多的方法中的代码。简而言之,这意味着async方法中出现在 an 之前的任何代码await都将作为普通同步代码运行。如果我们看看你的fetchEvents方法:
Future<void> fetchEvents(String email) async {
//await Future.delayed(const Duration(milliseconds: 100), (){});
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
Run Code Online (Sandbox Code Playgroud)
我们可以看到第一个await发生在调用EventService().getEventsByUser(email). 在此notifyListeners之前有一个,因此它将被同步调用。这意味着从小部件的 build 方法调用此方法就好像您notifyListeners在 build 方法本身中调用一样,正如我所说,这是禁止的。
当您添加调用时它起作用的原因Future.delayed是因为现在await在方法的顶部有一个,导致它下面的所有内容都异步运行。一旦执行到调用 的代码部分notifyListeners,Flutter 就不再处于重建小部件的状态,因此此时调用该方法是安全的。
您可以改为fetchEvents从该initState方法调用,但这会遇到另一个类似的问题:您也无法setState在小部件初始化之前调用。
那么,解决办法就是这样。与其通知所有侦听事件提供程序正在加载的小部件,不如在创建时默认加载它。(这很好,因为它在创建后做的第一件事就是加载所有事件,所以不应该出现在第一次创建时不需要加载的情况。)这消除了将提供者标记为的需要在方法开始时加载,这反过来又消除了在notifyListeners那里调用的需要:
EventLoadingStatus _eventLoadingStatus = EventLoadingStatus.Loading;
...
Future<void> fetchEvents(String email) async {
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
Run Code Online (Sandbox Code Playgroud)
问题是您notifyListeners在一个函数中调用了两次。我明白了,你想改变状态。但是,EventProvider不应该负责在加载时通知应用程序。你所要做的就是如果它没有加载,假设它正在加载,只需将CircularProgressIndicator. 不要notifyListeners在同一个函数中调用两次,这对你没有任何好处。
如果你真的想这样做,试试这个:
Future<void> fetchEvents(String email) async {
markAsLoading();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
void markAsLoading() {
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
22962 次 |
| 最近记录: |