我想加载事件列表,并在获取数据时显示加载指示器.

我正在try Provider模式(实际上是重构现有的应用程序).

因此,根据提供程序中管理的状态,事件列表显示是有条件的.

问题是,当我太快地拨打notifyListeners()时,会得到这样的异常:

═异常被基础库═捕获

为EventProvider调度通知时引发以下断言:

在生成过程中调用setState()或markNeedsBuild().

...

发送通知的EventProvider是:"EventProvider"的实例

在调用notifyListeners()之前等待几毫秒即可解决问题(请参阅下面Provider类中的注释行).

这是一个基于我的代码的简单示例(希望不要过于简化):

main function :

Future<void> main() async {
    runApp(
        MultiProvider(
            providers: [
                ChangeNotifierProvider(create: (_) => LoginProvider()),
                ChangeNotifierProvider(create: (_) => EventProvider()),
            ],
            child: MyApp(),
        ),
    );
}

root widget:

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

    }
}

home page:

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(...)
        )
    }
}

event provider:

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

有人能解释一下发生了什么事吗?

推荐答案

您将从根小部件的构建代码中调用fetchEvents.在fetchEvents内,您调用notifyListeners,其中包括对正在侦听事件提供程序的小部件调用setState.这是一个问题,因为当小部件处于重新构建过程中时,您不能在小部件上调用setState.

现在,您可能在想"但是fetchEvents方法被标记为async,所以它应该在以后异步运行".这个问题的答案是"既是又不是".async在DART中的工作方式是,当您调用async方法时,DART会try 同步运行该方法中尽可能多的代码.简而言之,这意味着async方法中在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();
}

我们可以看到,前await个发生在拨打EventService().getEventsByUser(email)的呼叫中.在那之前有一个notifyListeners,所以它会被同步呼叫.这意味着从小部件的build方法调用此方法将如同您在build方法本身中调用notifyListeners一样,正如我所说的,这是被禁止的.

当您将调用添加到Future.delayed时,它之所以能工作,是因为现在在方法的顶部有一个await,导致它下面的所有内容都异步运行.一旦执行到调用notifyListeners的代码部分,flatter就不再处于重建小部件的状态,因此在此时调用该方法是安全的.

您可以从initState方法调用fetchEvents,但这会遇到另一个类似的问题:在小部件初始化之前也不能调用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();
}

Flutter相关问答推荐

Flutter 中的多页表现

有没有一种正确的方法可以通过上下滑动在两个小部件(在我的情况下是应用程序栏)之间切换?

在setState之后,Ffltter ListView.Builder未更新

flutter -当使用SingleChildScrollView包装我的列小部件时,它不会填充整个高度

如何将Visual Studio代码中Flutter 应用的包名从com.example.XXX更改为Play Store中受限制的包名称

为什么用户界面不会在Flight中的复选框中更改?

dotenv:未处理的异常:FileNotFoundError的实例

如何将小部件代码移动到另一个文件以获得更好的可读性

Flutter中有没有办法区分鼠标左右平移?

尽管我的 url 链接正确,但 ArgumentError(无效参数:URI 文件中未指定主机:///)错误

如何在flutter中实现这样的底部导航栏?

「Flutter」错误:没有名为'kind'的命名参数

Flutter Dart 功能弹出错误的小部件?

如何从另一个页面访问 Firebase 值?

如何在 Flutter 中将列小部件添加到行小部件?

无法将参数类型Future Function(String?)分配给参数类型void Function(NotificationResponse)?

避免长单词中断

如何使用 Row 和 Column 制作 L 形布局?

Flutter 本地通知在最新版本中不起作用

Navigator.push '!_debugLocked' 上的错误:不正确