PlutoGrid不能替换行并重新构建网格

移动开发 2026-07-09

给定如下小部件:

import 'package:cerebellum/const.dart';
import 'package:cerebellum/models/api/patientfinder.dart';
import 'package:cerebellum/models/api/patientfindrec.dart';
import 'package:cerebellum/utils/dateutil.dart';
import 'package:flutter/material.dart';
import 'package:flutter_it/flutter_it.dart';
import 'package:pluto_grid/pluto_grid.dart';

class PatientFinderGridScreen extends StatefulWidget {
  final List<PatientFindRec> patients;

  const PatientFinderGridScreen({super.key, required this.patients});

  @override
  State<PatientFinderGridScreen> createState() =>
      _PatientFinderGridScreenState();
}

class _PatientFinderGridScreenState extends State<PatientFinderGridScreen> {
  final List<PlutoColumn> columns = [];
  final List<PlutoRow> rows = [];
  PlutoGridStateManager? stateManager;

  @override
  void initState() {
    super.initState();
    _buildGridColumns();
    _handleIncomingDataUpdate();
  }

  @override
  void didUpdateWidget(covariant PatientFinderGridScreen oldWidget) {
    super.didUpdateWidget(oldWidget);

    // TODO: what if the signal's value was not replaced but altered???
    if (widget.patients != oldWidget.patients) {
      _handleIncomingDataUpdate();
    }
  }

  void _handleIncomingDataUpdate() {
    _buildGridRows(widget.patients);
    if (stateManager != null) {
      stateManager!.refRows.clear();
      stateManager!.refRows.addAll(rows);
      stateManager!.notifyListeners();
    }

    // Tell the data model that the current patient has changed.
    // Not important for this question
    di<PatientFinderModel>().patient.value = widget.patients.isEmpty
        ? null
        : widget.patients[0];
  }

  void _buildGridRows(List<PatientFindRec> patientList) {
    rows.clear();    
    rows.addAll(
      patientList.map((patient) {
        return PlutoRow(
          cells: {
            'id': PlutoCell(value: patient.id),
            // more cell values are here...
          },
        );
      }).toList(),
    );
  }

  void _gridStateListener() {
    if (stateManager == null) return;

    final cr = stateManager!.currentRowIdx;
    // Tell the model that the current patient has changed
    // Not important for this question
    if (cr == null) {
      di<PatientFinderModel>().patient.value = null;
    } else {
      if (cr >= 0 && cr < widget.patients.length) {
        di<PatientFinderModel>().patient.value = widget.patients[cr];
      }
    }
  }

  @override
  void dispose() {
    stateManager?.removeListener(_gridStateListener);
    super.dispose();
  }

  void _onGridLoaded(PlutoGridOnLoadedEvent event) {
          stateManager = event.stateManager;
          stateManager!.setShowColumnFilter(false); // turn off in-memory filter
          stateManager!.addListener(_gridStateListener);
          // Tell the data model that the current patient has changed.
          di<PatientFinderModel>().patient.value = widget.patients.isEmpty
              ? null
              : widget.patients[0];
  }


  void _buildGridColumns() {
    columns.addAll([
      PlutoColumn(
        title: 'Identifier',
        field: 'id',
        type: PlutoColumnType.text(),
        enableFilterMenuItem: false,
        width: 140,
      ),
      // more columns are here...
    ]);
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: PlutoGrid(
        columns: columns,
        rows: rows,
        onLoaded: _onGridLoaded,
        // this is a constant value taken from the const module, not important for this question
        configuration: PlutoGridConfiguration(
          localeText: plutoLocaleText, 
          style: plutoGridStyle(context),
          shortcut: PlutoGridShortcut(),
        ),
      ),
    );
  }
}

注意:PatientFindRec 只是一个freezed类,PatientFinderModel 是一个全局实例,用于在 Signal 个实例中保存应用状态。

问题在于:初始时,patients 是一个空列表。当我把 patients 改成非空列表时,_PatientFinderGridScreenState._handleIncomingDataUpdate 会被调用,stateManager 是非空的,但网格在界面上并未更新。即使我向 stateManager.refRows 增加了100行,并且也调用了 stateManager.notifyListeners(),也什么都没有发生。网格只显示列头。

也尝试了下面这个,但不起作用:

  void _handleIncomingDataUpdate() {
    _buildGridRows(widget.patients);
    if (stateManager != null) {
      stateManager!.removeAllRows();
      stateManager!.appendRows(rows);
      stateManager!.notifyListeners();
    }

如果我把这个widget从 widget树中移除,然后在初始时使用一个非空的 patients 值重新挂载,它就能正确显示行。但我不能这么做,因为PlutoGrid在挂载时会抢走焦点。我不能改变焦点(因为网格显示的是实时搜索的结果,更新网格时焦点必须保持在搜索文本框内)。

那么,为什么网格在屏幕上没有更新呢?缺少了什么?

是否有关于PlutoGrid,特别是PlutoGridStateManager的权威文档?我所能找到的只有一些演示视频和所谓的API参考(https://pub.dev/documentation/pluto_grid/latest/pluto_grid/PlutoGridStateManager-class.html)。没有任何一个讲清楚状态管理器到底应该怎样使用。

解决方案

经过几个小时的折腾,我终于找到了答案。但首先,PlutoGrid的确有关于状态管理器的文档,只是因为某种原因我没能找到它:https://pluto.weblaze.dev/add-and-remove-columns-and-rows

我代码的问题在于PlutoGrid是用 rows: rows 初始化的。这意味着 _PatientFinderGridScreenState.rows 就是同一个对象。看起来状态管理器并不会创建它内部的行列表,它直接使用传给网格的那个列表。因此当我调用 stateManager!.removeAllRows() 时,它实际上是在清空同一个列表。它变成了空的。之后,当我调用 stateManager!.appendRows(rows) 时,它又从一个空列表中追加项目。结果,网格就变成空的。

结论:如果把一个列表传给 PlutoGrid,那么这个列表不应该再用于其他用途。你必须使用另一份列表来计算新行;如果你想调用 stateManager.appendRows()prependNewRows 等,也必须使用另一份列表。

在我的示例中,我把 _handleIncomingDataUpdate和 _buildGridRows合并成这个,现在它起作用了!

  void _buildGridRows(List<PatientFindRec> patientList) {
    final newRows = patientList.map((patient) {
        return PlutoRow(
          cells: {
            'id': PlutoCell(value: patient.id),
            // more cell values are here...
          },
        );    }).toList();
    // https://pluto.weblaze.dev/add-and-remove-columns-and-rows
    if (stateManager != null) {
      stateManager!.removeAllRows();
      stateManager!.appendRows(newRows);
      stateManager!.notifyListeners();
    }

    // Possibly select the patient that was previously selected?
    di<PatientFinderModel>().patient.value = widget.patients.isEmpty
        ? null
        : widget.patients[0];
  }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章