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

Flutter Provider无法实时更新购物车总价按钮显示求助

问题描述

开发了一个Provider用于计算多个Food类实例的总价并展示在按钮上,但按钮的总价只有在离开页面返回或热重载后才会更新。userOrders列表存储购物车中所有添加的商品,计划通过fold函数计算每个商品单价与数量的乘积之和,并将结果显示在按钮上。


现有代码

Food类

class Food {
  String imgUrl;
  String desc;
  String name;
  String waitTime;
  num score;
  int price;
  int pq;
  int quantity;
  bool favourited;
  List<Map<String, String>> ingredients;
  String about;
  bool highlight;
  Food(this.imgUrl, this.desc, this.name, this.pq, this.waitTime, this.score,
      this.price, this.quantity, this.ingredients, this.about, this.favourited,
      {this.highlight = false});

  int pqProduct(Food food) {
    pq = food.quantity * food.price;
    return pq;
  }
}

Provider类

class FavFood with ChangeNotifier {
  int peeQee() {
    int pqe =
        userOrders.fold(0, (a, element) => a + element.pqProduct(element));
    return pqe;
  }

  void update() {
    notifyListeners();
  }
}

按钮组件

class NextButton extends StatefulWidget {
  @override
  State<NextButton> createState() => _NextButtonState();
}

class _NextButtonState extends State<NextButton> {
  @override
  Widget build(BuildContext context) {
    return userOrders.isNotEmpty
        ? Container(
            color: Colors.transparent,
            padding: const EdgeInsets.fromLTRB(10.0, 10.0, 5.0, 5.0),
            height: 60,
            width: double.infinity,
            child: ElevatedButton(
              style: ButtonStyle(
                shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(18.0),
                    side: const BorderSide(color: Colors.transparent),
                  ),
                ),
              ),
              child: Text(
                '${context.read<FavFood>().peeQee}',
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 18.0,
                ),
              ),
              onPressed: () {},
            ),
          )
        : const Text('');
  }
}

Food类实例示例

Food(
        'assets/images/sha-modified.png',
        'Most Recommended',
        'Shawarma',
        0,
        '15 min',
        4.5,
        1500,
        0,
        [
          {
            'Flatbread': 'assets/images/flatbread-modified.png',
          },
          {'Cabbage': 'assets/images/cabbb-modified.png'},
          {'Chicken': 'assets/images/chicken-modified.png'},
          {'Sauce': 'assets/images/sauce-modified.png'},
        ],
        'A sandwich of sliced lamb or chicken, vegetables, and often tahini wrapped in pita bread',
        highlight: true,
        false,
      ),

问题原因分析

  1. 组件未监听Provider变化:按钮使用context.read<FavFood>(),该方法仅获取Provider实例但不监听状态变更,因此notifyListeners()触发时不会重建组件。
  2. 状态变更未自动通知:userOrders为全局变量,添加/修改商品时未主动调用Provider的notifyListeners(),无法触发UI更新。
  3. 不必要的实例属性修改:pqProduct方法直接修改Food实例的pq属性,既冗余又可能导致状态混乱。
  4. 全局状态管理失控:userOrders脱离Provider管理,无法统一控制状态变更的通知逻辑。

解决方案

1. 优化Food类

移除冗余的pq属性和pqProduct方法,直接通过计算属性获取单商品总价:

class Food {
  String imgUrl;
  String desc;
  String name;
  String waitTime;
  num score;
  int price;
  int quantity;
  bool favourited;
  List<Map<String, String>> ingredients;
  String about;
  bool highlight;

  Food(
    this.imgUrl,
    this.desc,
    this.name,
    this.waitTime,
    this.score,
    this.price,
    this.quantity,
    this.ingredients,
    this.about,
    this.favourited, {
    this.highlight = false,
  });

  // 计算单商品总价,不修改实例属性
  int get totalPrice => price * quantity;
}

2. 重构Provider类

将userOrders移至Provider内部管理,添加商品操作方法并自动触发状态通知:

class FavFood with ChangeNotifier {
  final List<Food> _userOrders = [];

  // 返回不可变副本,避免外部直接修改
  List<Food> get userOrders => [..._userOrders];

  // 计算购物车总价
  int get totalPrice => _userOrders.fold(0, (sum, food) => sum + food.totalPrice);

  // 添加商品(存在则增加数量,不存在则新增)
  void addFood(Food food) {
    final existingItem = _userOrders.firstWhere(
      (item) => item.name == food.name, // 可根据实际调整唯一标识
      orElse: () => Food('', '', '', '', 0, 0, 0, [], '', false),
    );

    if (existingItem.name.isNotEmpty) {
      existingItem.quantity += 1;
    } else {
      _userOrders.add(Food(
        food.imgUrl,
        food.desc,
        food.name,
        food.waitTime,
        food.score,
        food.price,
        1, // 默认添加数量为1
        food.ingredients,
        food.about,
        food.favourited,
        highlight: food.highlight,
      ));
    }
    notifyListeners(); // 触发UI更新
  }

  // 修改商品数量(数量≤0则移除)
  void updateQuantity(Food food, int newQuantity) {
    final existingItem = _userOrders.firstWhere(
      (item) => item.name == food.name,
      orElse: () => Food('', '', '', '', 0, 0, 0, [], '', false),
    );

    if (existingItem.name.isNotEmpty) {
      existingItem.quantity = newQuantity;
      if (existingItem.quantity <= 0) _userOrders.remove(existingItem);
      notifyListeners(); // 触发UI更新
    }
  }
}

3. 修改按钮组件

使用context.watch<FavFood>()监听状态变化,改为无状态组件简化代码:

class NextButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final favFood = context.watch<FavFood>();
    return favFood.userOrders.isNotEmpty
        ? Container(
            color: Colors.transparent,
            padding: const EdgeInsets.fromLTRB(10.0, 10.0, 5.0, 5.0),
            height: 60,
            width: double.infinity,
            child: ElevatedButton(
              style: ButtonStyle(
                shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(18.0),
                    side: const BorderSide(color: Colors.transparent),
                  ),
                ),
              ),
              child: Text(
                '${favFood.totalPrice}',
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 18.0,
                ),
              ),
              onPressed: () {},
            ),
          )
        : const Text('');
  }
}

4. 更新Food实例示例

移除冗余的pq参数:

Food(
  'assets/images/sha-modified.png',
  'Most Recommended',
  'Shawarma',
  '15 min',
  4.5,
  1500,
  0,
  [
    {'Flatbread': 'assets/images/flatbread-modified.png'},
    {'Cabbage': 'assets/images/cabbb-modified.png'},
    {'Chicken': 'assets/images/chicken-modified.png'},
    {'Sauce': 'assets/images/sauce-modified.png'},
  ],
  'A sandwich of sliced lamb or chicken, vegetables, and often tahini wrapped in pita bread',
  false,
  highlight: true,
),

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 20:07:10