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

