使用 Riverpod 将 Flutter 添加到状态

Flutter adding to state using Riverpod

在搜索并详细了解如何添加或更新数据结构的某些字段后,我们有以下实现

这里我们想要一个简单的 order 结构,其中包含用户可以将服务添加到订单或更新所选服务计数的数据,例如,在第一次用户未选择任何服务时,我们有 OrderStructure:[] 我们在 constructor:

中开头
OrderNotifier() : super(_initial);
static OrderStructure _initial = OrderStructure('', []);

当用户选择任何服务时,我们将三个参数传递给 increment 方法:

void increment({required int productId, required int serviceId, required int serviceCost})

如果服务不存在,应该添加属于产品的服务。每个服务都属于产品,我们用 productId 存储它 在 increment 方法中我们传递 productIdserviceIdserviceCost,它们可以搜索到 class 数据结构作为 OrderStructure。

如果 productId 存在,那么我们尝试搜索 serviceId,如果 serviceId 存在。 productId = true && serviceId = true 然后我们应该增加 count 值, 如果不存在,则应另存为新项目 请注意,我们不应该将多个相同的 productIdserviceId 放入 OrderStructure.

它们中的每一个都必须是唯一的。每个产品可以有多个服务,例如 ListOrderStructure 不应该有多个 productId

例如OrderStructure数据结构可以是:

OrderStructure:
   title : 'sample',
   products:[
       productId: 1
       services : [
         [serviceId:1, count:1, cost: 100],
         [serviceId:2, count:9, cost: 500],
         [serviceId:3, count:5, cost: 2000],
       ],
       productId: 2
       services : [
         [serviceId:3, count:10, cost: 100],
         [serviceId:5, count:6, cost: 500]
       ],
       productId: 3
       services : [
         [serviceId:3, count:10, cost: 100],
         [serviceId:5, count:6, cost: 500],
         [serviceId:6, count:6, cost: 500],
         [serviceId:7, count:6, cost: 500],
         [serviceId:8, count:6, cost: 500],
       ],
   ]

当用户选择 seriveId id 1 并且 productId id 是 1 时,我们在这个集合中搜索 productId 然后 serviceId, 如果它存在那么 count 应该递增,否则它应该作为一个新的

添加到 OrderStructure 集合中

例如:

OrderStructure:
   title : 'sample',
   products:[
       productId: 1
       services : [
         [serviceId:1, count:2, cost: 100],
         ...

我们有这段代码,但它不正确:

void increment({required int productId, required int serviceId, required int serviceCost}) {
  final newState = OrderStructure(state.title, [
    ...state.products,
    for (final product in state.products)
      if (product.id == productId)
        for (final service in product.services)
          if (service.id == serviceId)
            SelectedProducts(productId,
                [...product.services, SelectedProductServices(service.id, service.count + 1, service.cost)])
  ]);
  // ignore: iterable_contains_unrelated_type
  if (!newState.products.contains(productId) ||
      // ignore: iterable_contains_unrelated_type
      !newState.products.firstWhere((p) => p.id == productId).services.contains(serviceId)) {
    newState.products.add(
        SelectedProducts(serviceId, <SelectedProductServices>[SelectedProductServices(serviceId, 1, serviceCost)]));
  }
  state = newState;
}

完整代码:

void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context,ScopedReader watch) {
    final _orderProvider = watch(orderStateNotifierProvider.notifier);
    final _product = SelectedProducts(1, []);
    final _service = SelectedProductServices(1, 1, 111);
    return Scaffold(
      appBar: AppBar(
        title: Text('test'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            TextButton(
              child: Text('add new service'),
              onPressed: ()=>context.read(orderStateNotifierProvider.notifier)
                  .increment(productId:1, serviceId:1, serviceCost:100),
            ),

            // here we want to add new service to product, the same as above code, if productId doesn't exists
            // into `OrderStructure` it should be save with
            TextButton(
              child: Text('append service to product`s services'),
              onPressed: ()=>context.read(orderStateNotifierProvider.notifier)
                  .increment(productId:1, serviceId:2, serviceCost:500),
            ),

            Text('product`s service count: ${_orderProvider.state.products[0].services.length}')
          ],
        ),
      )
    );
  }
}


//------------------------


final orderStateNotifierProvider = StateNotifierProvider<OrderNotifier, OrderStructure>((ref) => OrderNotifier());

class OrderNotifier extends StateNotifier<OrderStructure> {
  OrderNotifier() : super(_initial);
  static OrderStructure _initial = OrderStructure('', []);

  void resetOrder() {
    _initial = const OrderStructure('', []);
  }

  void updateAddress({
    required String title,
  }) {
    state = OrderStructure(title,state.products);
  }


  void increment({required int productId, required int serviceId, required int serviceCost}) {
    final newState = OrderStructure(state.title, [
      ...state.products,
      for (final product in state.products)
        if (product.id == productId)
          for (final service in product.services)
            if (service.id == serviceId)
              SelectedProducts(productId,
                  [...product.services, SelectedProductServices(service.id, service.count + 1, service.cost)])
    ]);
    // ignore: iterable_contains_unrelated_type
    if (!newState.products.contains(productId) ||
        // ignore: iterable_contains_unrelated_type
        !newState.products.firstWhere((p) => p.id == productId).services.contains(serviceId)) {
      newState.products.add(
          SelectedProducts(serviceId, <SelectedProductServices>[SelectedProductServices(serviceId, 1, serviceCost)]));
    }
    state = newState;
  }
}

class OrderStructure {
  final String title;
  final List<SelectedProducts> products;

  const OrderStructure(this.title, this.products);
}

class SelectedProducts {
  final int id;
  final List<SelectedProductServices> services;

  SelectedProducts(this.id, this.services);
}

class SelectedProductServices {
  final int id;
  final int count;
  final int cost;

  const SelectedProductServices(this.id, this.count, this.cost);
}

您不需要更新整个 OrderStructure,只需更新包含要更新的服务的内部列表的引用:

void increment({required int productId, required int serviceId, required int serviceCost}) {
  final OrderStructure order = state;

  final int productIndex = order.products.indexWhere((product) => product.id == productId);

  final SelectedProducts product;
  // If the product has not been found, add it to the products
  if (productIndex == -1) {
    product = SelectedProducts(productId, []);
    order.products.add(product);

  // Otherwise, just get it
  } else {
    product = order.products[productIndex];
  }

  final int serviceIndex = product.services.indexWhere((service) => service.id == serviceId && service.cost == serviceCost);
  
  // If the service has not been found, add it to the services
  if (serviceIndex == -1) {
    product.services.add(SelectedProductServices(serviceId, 1, serviceCost));
  
  // Otherwise, just update its counter
  } else {
    final SelectedProductServices service = product.services[serviceIndex];
    product.services[serviceIndex] = SelectedProductServices(service.id, service.count + 1, service.cost);
  }

  // Set the state to the updated order structure
  state = order;
}

对于递减部分,在最后一行做service.count - 1即可。

我修复了问题,increment 方法应该是:

void increment(int productId, int serviceId, int serviceCost) {
  final newState = OrderStructure(state.title, state.address, state.lat, state.lang, state.pelak, state.vahed, []);
  for (final product in state.products) {
    if (product.id == productId) {
      if (product.services.any((s) => s.id == serviceId)) {
        final service = product.services.firstWhere((s) => s.id == serviceId);
        newState.products.add(SelectedProducts(productId, [
          ...product.services.where((s) => s.id != serviceId),
          SelectedProductServices(service.id, service.count + 1, service.cost)
        ]));
      } else {
        newState.products.add(
            SelectedProducts(productId, [...product.services, SelectedProductServices(serviceId, 1, serviceCost)]));
      }
    } else {
      newState.products.add(product);
    }
  }
  if (!newState.products.any((p) => p.id == productId) ||
      !newState.products.firstWhere((p) => p.id == productId).services.any((s) => s.id == serviceId)) {
    // Add new
    newState.products.add(SelectedProducts(productId, [SelectedProductServices(serviceId, 1, serviceCost)]));
  }
  state = newState;
}

感谢 TimWhiting

你需要做这样的事情。

void increment(int productId, int serviceId, int serviceCost) {
  // copy the old state
  final newState = OrderStructure(state.title, [...state.products]);
  bool updated = false;
  /// We directly iterate on the newState.
  for(final product in newState.products){
      if( product.id != productId ) {
          continue; // jump to next the product because the current product do not match.
       } 
      updated = true; // We are sure that the product already exist.
      // We search the first index of service that has `id` equal to `serviceId`.
      final serviceIndex = product.services.indexWhere( (s) => s.id == serviceId );
      if( serviceIndex >= 0 ) {
         product.services[serviceIndex].count ++;
         break;
      }
      final newService = SelectedProductServices(serviceId, 1, serviceCost);
      product.services.add(newService);
      break;
  }
  // Now we only check if we have not made an update.
  if(!update) {
    final newService = SelectedProductServices(serviceId, 1, serviceCost);
    final newProduct =  SelectedProduct(productId, [newService]);
    newState.products.add(newProduct); 
  }
}