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

Flutter(Riverpod)库存管理:点击多次才添加商品问题排查

商品库存管理App添加按钮异常问题修复

问题描述

开发的商品库存管理App中,增删改商品实现逻辑基本一致,但添加商品按钮需点击两次才生效,点击4次会重复添加3个相同商品。

相关代码

包含按钮的Widget代码

@override
Widget build(BuildContext context) {
  final currentItemList = ref.watch(jsonInventoryServiceNotifierProvider);
  return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text("Manually Add An Item"),
      ),
      body: Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            //TEXT FIELD HERE TO INPUT ITEM NAME
             ElevatedButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Item Added!')),
                  );
                  Item newItem = Item(
                    productName: productNameTextFieldController.text,
                    isActive: true,
                  );
                  await ref
                      .read(jsonInventoryServiceNotifierProvider.notifier)
                      .insertItem(newItem);
                  currentItemList.when(
                      data: (item) {
                        if(item.any((element) =>
                            element.productName.toLowerCase() ==
                            (newItem.productName.toLowerCase()))) {
                          print("OPEN DIALOG BOX TO SAY THIS PRODUCT WAS BOUGHT BEFORE");
                        }
                      },
                      error: (Object error, StackTrace stackTrace) {},
                      loading: () {});
                  setState(() {});
                }
              },
              child: const Text('Submit'),
            ),
          ],
      ),
  );
}

StateNotifierProvider类中的添加商品方法

Future<void> insertItem(Item item) async {
    final currentState = state;
    late var currentList;

    if (currentState is AsyncData<List<Item>>) {
      currentList = currentState.value;
      state = AsyncValue.data([...currentList, item]);
    } else if (currentState is AsyncLoading) {
    } else if (currentState is AsyncError) {
    }
    inventoryResult.results = currentList;
    final jsonmap = inventoryResult.toJson();
    storage.writeFile(jsonmap);
}

问题根源分析

  1. 状态更新与UI同步逻辑错误:
    • currentItemList是Widget构建时的旧状态,调用insertItem更新状态后,currentItemList.when仍基于旧状态判断,导致第一次点击的重复检查无效。
    • 手动调用setState(() {})属于冗余操作,Riverpod会自动监听状态变化重建Widget,手动触发会导致重建时机混乱,造成“点击两次才生效”的错觉。
  2. insertItem方法存在逻辑漏洞:
    • 当状态为AsyncLoading或AsyncError时,currentList未初始化就赋值给inventoryResult.results,会引发空值问题,且跳过状态更新。
    • 未处理storage.writeFile的异步操作,也未维护AsyncValue的加载状态,用户无法感知操作进度,容易重复点击。
  3. 无防重机制:按钮点击后未禁用,用户可多次触发点击事件,结合状态更新延迟,导致重复添加商品。

修复方案

1. 优化Widget代码(移除冗余setState,添加防重与状态同步)

// 定义防重的StateProvider,用于控制按钮状态
final _isAddButtonDisabled = StateProvider<bool>((ref) => false);

@override
Widget build(BuildContext context) {
  final currentItemList = ref.watch(jsonInventoryServiceNotifierProvider);
  final isButtonDisabled = ref.watch(_isAddButtonDisabled);

  return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Manually Add An Item"),
      ),
      body: Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            // TEXT FIELD HERE TO INPUT ITEM NAME
            ElevatedButton(
              onPressed: isButtonDisabled ? null : () async {
                if (_formKey.currentState!.validate()) {
                  // 点击后立即禁用按钮,防止重复操作
                  ref.read(_isAddButtonDisabled.notifier).state = true;

                  final newItem = Item(
                    productName: productNameTextFieldController.text.trim(),
                    isActive: true,
                  );

                  try {
                    // 执行添加操作
                    await ref
                        .read(jsonInventoryServiceNotifierProvider.notifier)
                        .insertItem(newItem);

                    // 基于最新状态检查商品是否存在
                    ref.read(jsonInventoryServiceNotifierProvider).when(
                      data: (items) {
                        if (!mounted) return;

                        final isDuplicate = items.any((element) =>
                            element.productName.toLowerCase() ==
                            newItem.productName.toLowerCase());

                        if (isDuplicate) {
                          // 显示重复提示弹窗
                          showDialog(
                            context: context,
                            builder: (ctx) => AlertDialog(
                              title: const Text("提示"),
                              content: const Text("该商品已存在!"),
                              actions: [
                                TextButton(
                                  onPressed: () => Navigator.pop(ctx),
                                  child: const Text("确定"),
                                )
                              ],
                            ),
                          );
                        } else {
                          // 添加成功提示并清空输入框
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('商品添加成功!')),
                          );
                          productNameTextFieldController.clear();
                        }
                      },
                      error: (error, stack) {
                        if (!mounted) return;
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(content: Text('添加失败:${error.toString()}')),
                        );
                      },
                      loading: () {},
                    );
                  } finally {
                    // 无论成功失败,恢复按钮状态
                    if (mounted) {
                      ref.read(_isAddButtonDisabled.notifier).state = false;
                    }
                  }
                }
              },
              child: const Text('Submit'),
            ),
          ],
        ),
      ),
  );
}

2. 修复StateNotifierProvider的insertItem方法

Future<void> insertItem(Item item) async {
  // 先将状态置为加载中,明确告知UI操作进行中
  state = const AsyncLoading();
  try {
    List<Item> currentList;
    final currentState = state;

    // 统一处理不同状态下的列表获取逻辑
    if (currentState is AsyncData<List<Item>>) {
      currentList = currentState.value;
    } else {
      // 从本地存储读取初始列表,避免空值
      currentList = inventoryResult.results ?? [];
    }

    // 先检查商品是否已存在(可选,也可在UI层判断)
    final isDuplicate = currentList.any((element) =>
        element.productName.toLowerCase() == item.productName.toLowerCase());
    if (isDuplicate) {
      // 恢复原状态并抛出错误,让UI层处理重复提示
      state = AsyncData(currentList);
      throw Exception("该商品已存在");
    }

    // 生成新列表并更新状态
    final newList = [...currentList, item];
    state = AsyncData(newList);

    // 更新本地存储,等待异步写入完成
    inventoryResult.results = newList;
    final jsonMap = inventoryResult.toJson();
    await storage.writeFile(jsonMap);
  } catch (e) {
    // 捕获错误并更新状态为错误状态
    state = AsyncError(e, StackTrace.current);
    rethrow; // 抛出错误让UI层处理
  }
}

3. 额外优化建议

  • 输入框处理:添加商品成功后清空输入框,提升用户操作流畅度。
  • Context有效性判断:异步操作后通过mounted判断Widget是否存活,避免context失效引发报错。
  • 状态统一管理:完全依赖Riverpod维护状态,移除所有setState调用,确保状态一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:34:54