You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PlutoGrid数据更新后界面不渲染问题及文档咨询

PlutoGrid数据更新不渲染问题排查与解答

场景与代码说明

给定如下Flutter组件代码:

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为空列表,当将其更新为非空列表时,_PatientFinderGridScreenState._handleIncomingDataUpdate方法会被调用,且stateManager不为空,但PlutoGrid仅显示列标题,行数据未在界面渲染。

曾尝试以下修改,但均无效:

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

若将组件从Widget树移除后重新挂载(传入初始非空的patients),网格可正常显示行数据,但该操作会导致PlutoGrid获取焦点,不符合需求(需保持焦点在搜索框中,因网格展示实时搜索结果)。

解答

1. 网格数据更新后未渲染的原因及解决方案

核心原因

  • 错误的行数据更新方式:直接操作stateManager.refRows或使用removeAllRows+appendRows未正确触发PlutoGrid的内部状态更新逻辑,导致UI未同步刷新。
  • 列表更新判断逻辑缺陷:原didUpdateWidget中仅通过widget.patients != oldWidget.patients判断列表是否变化,若仅修改原列表内容而非替换整个列表对象,该判断会失效,无法触发数据更新。

修复方案

(1)使用官方推荐的行更新方法

将_handleIncomingDataUpdate中的行更新逻辑替换为stateManager.setRows方法,该方法会自动处理内部状态同步并触发UI重绘:

void _handleIncomingDataUpdate() {
  _buildGridRows(widget.patients);
  if (stateManager != null) {
    stateManager!.setRows(rows); // 替换为setRows方法,内部已包含状态通知
  }

  di<PatientFinderModel>().patient.value = widget.patients.isEmpty
      ? null
      : widget.patients[0];
}
(2)修复列表更新判断逻辑

导入package:flutter/foundation.dart中的listEquals,通过比较列表内容而非对象引用判断数据是否变化(因PatientFindRec是freezed类,默认支持正确的相等性判断):

import 'package:flutter/foundation.dart'; // 添加导入

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

  // 替换原判断,确保列表内容变化时也能触发更新
  if (!listEquals(widget.patients, oldWidget.patients)) {
    _handleIncomingDataUpdate();
  }
}

2. PlutoGrid相关文档资源

目前PlutoGrid的官方文档确实以API参考和演示项目为主,缺乏系统性的详细教程,可通过以下途径获取更多实用信息:

  • 查看官方GitHub仓库的example目录:包含大量覆盖各种使用场景的示例代码,是学习的核心资源。
  • 浏览GitHub仓库的Issues页面:很多常见问题已有成熟解决方案,可通过关键词搜索类似问题。
  • 参与开发者社区交流:和其他使用PlutoGrid的开发者讨论经验,解决实际使用中的问题。

内容的提问来源于stack exchange,提问作者nagylzs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 07:27:26