setState在构建器函数中似乎不起作用

时间:2019-08-02 07:16:18

标签: flutter dart

setState实际如何工作?

当应该重建的Widget被构建在builder函数中时,它似乎并没有达到我的预期。我当前遇到的问题是ListView.builder和AlertDialog中的按钮。

此处的按钮之一是“自动清洁”,它将自动从对话框中显示的列表中删除某些项目。

注意:此处的目的是显示一个确认,其中包含将要提交的“作业”列表。作业被标记为显示哪些作业无效。用户可以返回上一步以更新参数,或者按“自动清理”以删除无效的参数。

onTap上的按钮如下:

    GeneralButton(
      color: Colors.yellow,
      label: 'Clear Overdue',
      onTap: () {
        print('Nr of jobs BEFORE: ${jobQueue.length}');

        for (int i = jobQueue.length - 1; i >= 0; i--) {
          print('Checking item at $i');
          Map task = jobQueue[i];
          if (cuttoffTime.isAfter(task['dt'])) {
            print('Removing item $i');
            setState(() {                             // NOT WORKING
              jobQueue = List<Map<String, dynamic>>.from(jobQueue)
                ..removeAt(i);                        // THIS WORKS
            });

          }
        }

        print('Nr of jobs AFTER: ${jobQueue.length}');
        updateTaskListState();                        // NOT WORKING 
        print('New Task-list state: $taskListState');
      },
    ),

jobQueue用作构建ListView的源。

updateTaskListState看起来像这样:

  void updateTaskListState() {
    DateTime cuttoffTime = DateTime.now().add(Duration(minutes: 10));
    if (jobQueue.length == 0) {
      setState(() {
        taskListState = TaskListState.empty;
      });
      return;
    }
    bool allDone = true;
    bool foundOverdue = false;
    for (Map task in jobQueue) {
      if (task['result'] == null) allDone = false;
      if (cuttoffTime.isAfter(task['dt'])) foundOverdue = true;
    }
    if (allDone) {
      setState(() {
        taskListState = TaskListState.done;
      });
      return;
    }
    if (foundOverdue) {
      setState(() {
        taskListState = TaskListState.needsCleaning;
      });
      return;
    }
    setState(() {
      taskListState = TaskListState.ready;
    });
  }

TaskListState只是用于确定作业队列是否准备好提交的枚举。

一旦taskListState设置为TaskListState.ready,“ Submit”按钮应变为活动状态。 AlertDialog按钮行使用taskListState来实现,就像这样:

  Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    mainAxisSize: MainAxisSize.max,
    children: <Widget>[
      if (taskListState == TaskListState.ready)
        ConfirmButton(
            onTap: (isValid && isOnlineNow)
                ? () {
                  postAllInstructions().then((_) {
                    updateTaskListState();
                    // navigateBack();
                  });
                : null),

从控制台输出中,我可以看到这正在发生,但是没有起作用。它似乎与同一问题有关。

当我使用build内的简单窗口小部件树构建所有窗口小部件时,似乎没有这种问题。但是在这种情况下,我无法更新对话框的显示,以显示没有删除项目的新列表。

这篇文章越来越长,但是AleryDialog内的ListView构建器看起来像这样:

  Flexible(
    child: ListView.builder(
      itemBuilder: (BuildContext context, int itemIndex) {
        DateTime itemTime = jobQueue[itemIndex]['dt'];
        bool isPastCutoff = itemTime.isBefore(cuttoffTime);
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            Text(
              userDateFormat.format(itemTime),
              style: TextStyle(
                color:
                    isPastCutoff ? Colors.deepOrangeAccent : Colors.blue,
              ),
            ),
            Icon(
              isPastCutoff ? Icons.warning : Icons.cached,
              color: isPastCutoff ? Colors.red : Colors.green,
            )
          ],
        );
      },
      itemCount: jobQueue.length,
    ),
  ),

但是由于带有按钮的Row()也不会对setState做出反应,所以我怀疑问题出在构建器函数本身内。

FWIW所有代码都驻留在Screen的State类中,除了“ GeneralButton”之类的一些项目(只是样板小部件)之外。

我的直觉是,这与jobQueue没有传递给任何小部件这一事实有关。构建器函数引用jobQueue [itemIndex],在此直接访问jobQueue属性。

我可能会尝试将AlertDialog提取到外部小部件中。这样做将意味着它只有在传递给Widget的构造函数的情况下才能访问jobQueue。...

1 个答案:

答案 0 :(得分:1)

由于您正在编写使用对话框的情况,因此这可能是造成问题的原因:

Snippet from Flutter API

https://api.flutter.dev/flutter/material/showDialog.html

对话框中的<key>NSExtensionActivationRule</key> <string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.item" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.content" ).@count == $extensionItem.attachments.@count ).@count > 0 </string> 调用因此不会触发对话框内容的所需UI重建。如API中所述,在另一个setState中实现重建的一种简便快捷的方法是使用context小部件:

StatefulBuilder

编辑

在编程世界中几乎每种情况下,都有各种方法来处理showDialog( context: context, builder: (dialogContext) { return StatefulBuilder( builder: (stateContext, setInnerState) { // return your dialog widget - Rows in ListView in Container ... // call it directly as part of onTap of a widget of yours or // pass the setInnerState down to another widgets setInnerState((){ ... }) } ); 调用以更新对话框UI。这在很大程度上取决于您决定管理数据流/管理和逻辑分离的一般方式。例如,我使用您的setInnerState小部件(假设它是GeneralButton):

StatefulWidget

如果class GeneralButton extends StatefulWidget { // all your parameters ... // your custom onTap you provide as instantiated final VoidCallback onTap; GeneralButton({..., this.onTap}); @override State<StatefulWidget> createState() => _GeneralButtonState(); } class _GeneralButtonState extends State<GeneralButton> { ... @override Widget build(BuildContext context) { // can be any widget acting as a button - Container, GestureRecognizer... return MaterialButton( ... onTap: { // your button logic which has either been provided fully // by the onTap parameter or has some fixed code which is // being called every time ... // finally calling the provided onTap function which has the // setInnerState call! widget.onTap(); }, ); } 小部件中没有固定逻辑,则可以编写:GeneralButton

这将导致您按如下方式使用GeneralButton:

onTap: widget.onTap