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

Flutter中调用update方法后Provider未通知监听器致UI不更新

问题原因

你的DeliveryWidget在initState中初始化了本地状态_data,之后没有监听DeliveryProvider的变化。即便Provider调用了notifyListeners(),DeliveryWidget也不会同步更新_data,导致UI无法展示最新的配送状态。

解决方案

方案1:直接基于Provider数据构建UI(推荐,无本地状态冗余)

移除本地_data状态,每次构建时从Provider获取最新数据生成Item列表,确保UI始终与Provider数据同步:

class DeliveryWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<DeliveryProvider>(
      builder: (context, provider, child) {
        if (provider.deliveries.isEmpty) {
          return const Center(child: Text('No data found'));
        }
        // 每次构建时从Provider拉取最新数据生成Item
        final List<Item> data = provider.deliveries.map(generateItem).toList();
        
        return SingleChildScrollView(
          child: _buildPanel(context, data),
        );
      },
    );
  }

  Widget _buildPanel(BuildContext context, List<Item> data) {
    var width = MediaQuery.of(context).size.width;
    return ExpansionPanelList(
      expansionCallback: (int index, bool isExpanded) {
        // 注意:此方案下切换标签或数据更新后,展开状态会重置。若需保留展开状态,用方案2
        data[index].isExpanded = !isExpanded;
        // 强制刷新UI
        (context as Element).markNeedsBuild();
      },
      children: data.map<ExpansionPanel>((Item item) {
        return ExpansionPanel(
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              leading: FittedBox(
                fit: BoxFit.fitWidth,
                child: Text(
                  item.delivery.orderNr,
                  style: const TextStyle(
                      fontSize: 10,
                      fontWeight: FontWeight.bold,
                      color: Colors.red),
                ),
              ),
              title: FittedBox(
                fit: BoxFit.fitWidth,
                child: Text(
                    'from: ${item.delivery.origin}\nto: ${item.delivery.destination}'),
              ),
              trailing: SizedBox(
                width: 100,
                child: Row(children: [
                  Container(
                    width: 12,
                    height: 12,
                    decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: getStatusColor(item.delivery.status)),
                  ),
                  const SizedBox(width: 10),
                  Text(item.delivery.status)
                ]),
              ),
            );
          },
          body: ListTile(
            leading: SizedBox(
              width: width * 0.3,
              child: Text('Weight: ${item.delivery.weight} kg'),
            ),
            title: SizedBox(width: width * 0.4, child: Text(item.delivery.type)),
            subtitle: SizedBox(
                width: width * 0.4,
                child: Text('HxWxD: ${item.delivery.dimensions}')),
            trailing: SizedBox(
                width: width * 0.2,
                child: FittedBox(
                  fit: BoxFit.fitWidth,
                  child: Column(
                    children: [
                      Row(
                        children: [
                          IconButton(onPressed: () => null, icon: Icon(Icons.comment)),
                          IconButton(onPressed: () => null, icon: Icon(Icons.person))
                        ],
                      ),
                      CupertinoButton(
                        padding: EdgeInsets.zero,
                        onPressed: () => Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => UpdateDeliveryStatusScreen(),
                            settings: RouteSettings(
                              arguments: {'delivery': item.delivery.toJson()},
                            ),
                          ),
                        ),
                        child: Text(getButtonText(item.delivery.status)),
                      )
                    ],
                  ),
                )),
          ),
          isExpanded: item.isExpanded,
        );
      }).toList(),
    );
  }

  Color? getStatusColor(String status) {
    if (status == 'Received') return Colors.green;
    if (status == 'Dispatched') return Colors.yellow;
    if (status == 'In Transit') return Colors.orange;
    if (status == 'Delivered') return Colors.red;
    return null;
  }

  String getButtonText(String status) {
    return status == 'In Transit' ? 'pin code' : 'update';
  }
}

class Item {
  Delivery delivery;
  bool isExpanded;

  Item({required this.delivery, this.isExpanded = false});
}

Item generateItem(Delivery delivery) {
  return Item(delivery: delivery);
}

方案2:监听Provider变化更新本地状态(保留展开状态)

如果需要保留ExpansionPanel的展开/折叠状态,可通过本地映射维护状态,并在Provider变化时同步更新数据:

class DeliveryWidget extends StatefulWidget {
  @override
  State<DeliveryWidget> createState() => _DeliveryWidgetState();
}

class _DeliveryWidgetState extends State<DeliveryWidget> {
  List<Item> _data = [];
  Map<String, bool> _expansionStates = {}; // 维护每个配送项的展开状态

  Future<void> _syncDataWithProvider() async {
    final provider = Provider.of<DeliveryProvider>(context, listen: false);
    // 首次加载数据
    if (provider.deliveries.isEmpty) {
      await provider.getDeliveries(context);
    }
    setState(() {
      _data = provider.deliveries.map((delivery) {
        // 恢复之前的展开状态
        final isExpanded = _expansionStates[delivery.id] ?? false;
        return Item(delivery: delivery, isExpanded: isExpanded);
      }).toList();
    });
  }

  @override
  void initState() {
    super.initState();
    _syncDataWithProvider();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 监听Provider数据变化,同步更新本地数据
    Provider.of<DeliveryProvider>(context, listen: true);
    _syncDataWithProvider();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: _data.isNotEmpty
          ? _buildPanel()
          : const Center(child: Text('No data found')),
    );
  }

  Widget _buildPanel() {
    var width = MediaQuery.of(context).size.width;
    return ExpansionPanelList(
      expansionCallback: (int index, bool isExpanded) {
        setState(() {
          _data[index].isExpanded = !isExpanded;
          // 保存展开状态到映射
          _expansionStates[_data[index].delivery.id] = !isExpanded;
        });
      },
      children: _data.map<ExpansionPanel>((Item item) {
        // 此处代码与原_buildPanel中的Item构建逻辑一致,省略重复代码
      }).toList(),
    );
  }

  // 保留原getStatusColor、getButtonText方法
}

额外修正

Item类无需继承ChangeNotifier(未用到其通知能力),直接改为普通数据类即可,如上述代码所示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 04:30:02