【问题标题】:Exception setState() or markNeedsBuild() called during build构建期间调用的异常 setState() 或 markNeedsBuild()
【发布时间】:2021-08-07 04:57:39
【问题描述】:

一切正常,功能齐全,除了我在构建时抛出以下异常:

The following assertion was thrown while dispatching notifications for DatePickModel: setState() or markNeedsBuild() called during build.

我尝试搜索错误并发现了类似的情况,但我无法使用 Riverpod 轻松应用于这种情况。而且我确信这没什么大不了的,但是我读到如果不处理这可能会造成循环。

有什么办法解决吗?

小部件

import 'package:flutter/material.dart';
import 'package:date_range_picker/date_range_picker.dart' as DateRangePicker;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';

final dateController = ChangeNotifierProvider((ref) => DatePickModel());

class DateRangeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return IconButton(
        color: Colors.white,
        icon: Icon(Icons.calendar_today),
        onPressed: () async {
          final List<DateTime> picked = await DateRangePicker.showDatePicker(
              context: context,
              initialFirstDate: context.read(dateController).startDate,
              initialLastDate: context.read(dateController).endDate,
              firstDate: DateTime(2015),
              lastDate: DateTime(DateTime.now().year + 2));
          if (picked != null && picked.length == 2) {
            context.read(dateController).setDates(picked.first, picked.last);
          }
        });
  }
}

class DatePickModel extends ChangeNotifier {
  DateTime _startDate = DateTime.now().subtract(Duration(days: 7));
  DateTime _endDate = DateTime.now();

  String startFormatted;
  String endFormatted;

  DateTime get startDate => _startDate;
  DateTime get endDate => _endDate;

  void setDates(DateTime startDate, DateTime endDate) {
    _startDate = startDate;
    _endDate = endDate;
    notifyListeners();
  }

  void formatDates() {
    startFormatted = DateFormat.yMMMd().format(_startDate);
    endFormatted = DateFormat.yMMMd().format(_endDate);
    notifyListeners();
  }

  void resetDates() {
    _startDate = DateTime.now().subtract(Duration(seconds: 7));
    _endDate = DateTime.now();
    notifyListeners();
  }
}

屏幕

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linechart/date_range_widget.dart';
import 'package:linechart/linechart.dart';

class LineChartScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: AppBarTitle(),
          backgroundColor: Colors.lightBlue,
          actions: [DateRangeWidget()]),
      body: Column(
        children: [
          SafeArea(
            child: Center(
                child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: LineChart(),
            )),
          ),
        ],
      ),
    );
  }
}

class AppBarTitle extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    context.read(dateController).formatDates();
    final formStart = watch(dateController).startFormatted;
    final formEnd = watch(dateController).endFormatted;
    return Text('INCOME GRAPH --- $formStart - $formEnd');
  }
}

【问题讨论】:

    标签: flutter dart exception riverpod


    【解决方案1】:

    首先,避免在build方法中使用context.read,而是使用ConsumerWidget的watch参数。例如,您真的只想在onPressed 之类的回调函数中读取 提供程序(而不是监视)。链接的文档对此进行了更深入的介绍。

    确定后,您收到异常的原因是您正在更新小部件尝试构建时的状态。

    避免这种情况的最简单方法是使用addPostFrameCallback 确保在初始构建后更新状态:

    @override
    Widget build(BuildContext context, ScopedReader watch) {
      WidgetsBinding.instance?.addPostFrameCallback((_) {
        context.read(dateController).formatDates();
      });
      ...
    }
    

    请注意,context.read 在这种情况下是正确的,因为我们在回调函数中使用它。

    编辑: 您也可以在设置日期时对其进行格式化,如下所示:

    class DatePickModel extends ChangeNotifier {
      ...
      void setDates(DateTime startDate, DateTime endDate) {
        _startDate = startDate;
        _endDate = endDate;
        formatDates(notify: false);
        notifyListeners();
      }
    
      void formatDates({bool notify = true}) {
        startFormatted = DateFormat.yMMMd().format(_startDate);
        endFormatted = DateFormat.yMMMd().format(_endDate);
        if (notify) notifyListeners();
      }
      ...
    }
    

    【讨论】:

    • ScopedReader 是必须的吗?我不想总是重建整个小部件。也许如果我将小部件创建为一种可行的方法。但是当我后来选择使用消费者时,我觉得仍然可能会出现问题……我在这里的想法是否正确?我在这里问不是无礼。我还是个编程菜鸟
    • @RobbB 哈哈,不用担心!你可以做的是将这一切都放在Consumer 而不是ConsumerWidget 中,这只会重建Consumer 的后代。不过,为什么不只是在设置日期时格式化它们呢?我将编辑我的答案以包含我的想法。
    • 终于有机会试试这个了。它完美地工作亚历克斯!谢谢您的帮助!我很欣赏描述性答案而不是代码修复。学习很多:)
    猜你喜欢
    • 2021-08-08
    • 2020-10-05
    • 2018-05-15
    • 2020-12-12
    • 2020-12-28
    • 2021-01-06
    • 2021-06-06
    • 2021-05-31
    相关资源
    最近更新 更多