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
相关产品推荐
相关产品推荐

