Flutter结合Riverpod:如何仅更新模型中的单个属性?
Riverpod 实现产品多选状态管理方案
从 GetX 切换到 Riverpod 核心要适应不可变状态优先的设计思路,不能像 GetX 那样直接修改对象的 Rx 属性。针对你异步加载产品列表并实现多选的需求,提供两种实用方案:
方案一:将产品列表与选中状态绑定管理
这种方式适合需要将选中状态和产品数据耦合的场景,通过 StateNotifier 统一管理异步加载状态和选中状态:
1. 改造不可变的 ProductModel
添加 copyWith 方法,用于生成新的对象(Riverpod 要求状态不可变,不能直接修改原对象属性):
class ProductModel { final String name; final bool isSelected; ProductModel({ required this.name, required this.isSelected, }); // 生成新对象的拷贝方法,用于修改选中状态 ProductModel copyWith({ String? name, bool? isSelected, }) { return ProductModel( name: name ?? this.name, isSelected: isSelected ?? this.isSelected, ); } }
2. 创建 StateNotifier 管理状态
class ProductsState { final AsyncValue<List<ProductModel>> productsAsync; ProductsState({required this.productsAsync}); } class ProductsNotifier extends StateNotifier<ProductsState> { ProductsNotifier() : super(ProductsState(productsAsync: const AsyncLoading())); // 异步加载产品列表 Future<void> loadProducts(String orderId) async { state = state.copyWith(productsAsync: const AsyncLoading()); try { // 替换为你的接口调用逻辑 final apiProducts = await fetchProductsFromApi(orderId); // 初始化所有产品为未选中状态 final initialProducts = apiProducts.map((p) => ProductModel(name: p.name, isSelected: false)).toList(); state = state.copyWith(productsAsync: AsyncData(initialProducts)); } catch (e) { state = state.copyWith(productsAsync: AsyncError(e, StackTrace.current)); } } // 切换指定索引产品的选中状态 void toggleProductSelection(int index) { state.productsAsync.whenData((products) { final updatedProducts = List<ProductModel>.from(products); updatedProducts[index] = updatedProducts[index].copyWith( isSelected: !updatedProducts[index].isSelected, ); state = state.copyWith(productsAsync: AsyncData(updatedProducts)); }); } } // 关联 orderId 的家族 Provider final productsNotifierProvider = StateNotifierProvider.family<ProductsNotifier, ProductsState, String>((ref, orderId) { final notifier = ProductsNotifier(); notifier.loadProducts(orderId); return notifier; });
3. UI 层调用
@override Widget build(BuildContext context, WidgetRef ref) { final productsState = ref.watch(productsNotifierProvider(orderId)); return productsState.productsAsync.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('加载失败: $error'), data: (products) { return Scaffold( body: ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return ExpansionTile( leading: InkWell( customBorder: const CircleBorder(), onTap: () { // 调用 notifier 切换选中状态 ref.read(productsNotifierProvider(orderId).notifier).toggleProductSelection(index); }, child: Icon( product.isSelected ? Icons.check_circle_rounded : Icons.circle_outlined, color: Colors.green, ), ), title: Text(product.name), ); }, ), ); }, ); }
方案二:分离产品数据与选中状态(推荐)
这种方式更符合 Riverpod 的单一职责原则,用单独的 StateProvider 管理选中的产品 ID 集合,产品模型不需要包含选中状态:
1. 简化 ProductModel
class ProductModel { final String id; // 产品唯一标识,必须要有 final String name; ProductModel({ required this.id, required this.name, }); }
2. 创建两个独立的 Provider
// 仅负责异步加载产品列表的 FutureProvider final productsProvider = FutureProvider.family<List<ProductModel>, String>((ref, orderId) async { return fetchProductsFromApi(orderId); // 替换为你的接口调用 }); // 管理选中产品 ID 集合的 StateProvider final selectedProductIdsProvider = StateProvider.family<Set<String>, String>((ref, orderId) => {});
3. UI 层调用
@override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productsProvider(orderId)); final selectedIds = ref.watch(selectedProductIdsProvider(orderId)); return productsAsync.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('加载失败: $error'), data: (products) { return Scaffold( body: ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; final isSelected = selectedIds.contains(product.id); return ExpansionTile( leading: InkWell( customBorder: const CircleBorder(), onTap: () { final notifier = ref.read(selectedProductIdsProvider(orderId).notifier); if (isSelected) { notifier.state.remove(product.id); } else { notifier.state.add(product.id); } }, child: Icon( isSelected ? Icons.check_circle_rounded : Icons.circle_outlined, color: Colors.green, ), ), title: Text(product.name), ); }, ), ); }, ); }
方案对比
- 方案一:状态耦合,适合简单场景,修改选中状态时需要生成新的产品列表。
- 方案二:状态分离,更灵活易维护,后续扩展选中逻辑(如全选、反选)更方便,推荐使用。
内容的提问来源于stack exchange,提问作者Naografix
相关产品推荐
相关产品推荐

