如何在Riverpod中“部分”更新AsyncValue?

huangapple go评论73阅读模式
英文:

How to ("partially") update AsyncValue in riverpod?

问题

The riverpod (v2) documentation contains two great examples how a TODO-list could be implemented using either a Notifier or an AsyncNotifier. Both examples are functionally equivalent.

To pick one particular detail the non-async example contains a remove method like this

// Let's allow removing todos
void removeTodo(String todoId) {
  // Again, our state is immutable. So we're making a new list instead of
  // changing the existing list.
  state = [
    for (final todo in state)
      if (todo.id != todoId) todo,
  ];
}

Whereas the async version looks like this

// Let's allow removing todos
Future<void> removeTodo(String todoId) async {
  state = const AsyncValue.loading();
  state = await AsyncValue.guard(() async {
    await http.delete('api/todos/$todoId');
    return _fetchTodo();
  });
}

I'd now like to modify the async version to remove the deleted TODO item from its internal state instead of re-fetching the whole collection via HTTP (which is what _fetchTodo does). I guess a practical reason could be to implement something like optimistic updates, but in this case it's rather a learning experience for me.

英文:

The riverpod (v2) documentation contains two great examples how a TODO-list could be implemented using either a Notifier or an AsyncNotifier. Both examples are functionally equivalent.

To pick one particular detail the non-async example contains a remove method like this

// Let&#39;s allow removing todos
void removeTodo(String todoId) {
  // Again, our state is immutable. So we&#39;re making a new list instead of
  // changing the existing list.
  state = [
    for (final todo in state)
      if (todo.id != todoId) todo,
  ];
}

Whereas the async version looks like this

// Let&#39;s allow removing todos
Future&lt;void&gt; removeTodo(String todoId) async {
  state = const AsyncValue.loading();
  state = await AsyncValue.guard(() async {
    await http.delete(&#39;api/todos/$todoId&#39;);
    return _fetchTodo();
  });
}

I'd now like to modify the async version to remove the deleted TODO item from it's internal state instead of re-fetching the whole collection via HTTP (which is what _fetchTodo does). I guess a practical reason could be to implement something like optimistic updates, but in this case it's rather a learning experience for me.

答案1

得分: 2

如果我理解正确,你想在不重新从API/scratch获取所有内容的情况下,从状态中删除待办事项。在这种情况下:

  1. 你需要在状态中找到待办事项所在的列表,
  2. 复制该列表,
  3. 从列表中删除特定的待办事项,
  4. 使用第2步中找到的待办事项更新状态中的列表。

假设你的状态中有一个名为 List<ToDoItem> listOfTodoItems,这是如何删除该项目的方法:

void updateToDoList(ToDoItem itemToRemove) {
    // 第1步:找到我们要查找列表中的项的索引
    final indexOfSearchItem = listOfToDoItems.indexWhere((currentToDoItem) => currentToDoItem == itemToRemove);

    // 第2步:复制整个列表,以便用更新后的列表替换旧列表
    final listToUpdate = state.listOfTodoItems;

    // 第3步:从要更新的列表中删除该项
    listToUpdate.removeAt(indexOfSearchItem);

    // 第4步:使用新的更新后的列表更新状态的列表
    state = state.copyWith(listOfTodoItems: listToUpdate);
}

需要注意的是,我直接使用了 Equatable,但也可以通过重写 == 运算符和 hashCode 方法来实现相同的效果。

我直接使用了 copyWith 方法,假设你的数据类中已经有此方法,或者是使用 freezed 包生成的。

我使用了一个简单的 ToDoItem 示例,但这个想法适用于大多数类似情况。在处理数据类中的嵌套列表的更复杂情况下,可能需要更多的工作。

这种方式只在状态管理边界内删除数据,不会发送/接收已更新的 ToDoItem 到/从后端。

英文:

If I understood correctly, you want to remove a TODO item from the list in the state without re-fetching everything from the API/scratch. In that case:

  1. You'd need to find the item inside the list inside the state,
  2. Copy that list,
  3. Remove that specific item in the list and
  4. Update the state.list with the item (found in step 2) removed.

Assuming you have a List&lt;ToDoItem&gt; listOfTodoItems inside your state, this is how you'd remove that item:

void updateToDoList(ToDoItem itemToRemove){
    //Step 1: find the index of the item we&#39;re searching the list for
    final indexOfSearchItem = listOfToDoItems.indexWhere((currentToDoItem)=&gt; currentToDoItem == ToDoItem(toDoValue: &#39;Math class&#39;));

    //Step 2: copy the whole list so we can replace the old list with the updated one
    final listToUpdate = state.listOfTodoItems;

    //Step 3: remove the item from the list we&#39;re going to update
    listToUpdate.removeAt(indexOfSearchItem);

    //Step 4: update the state&#39;s list with the new updated list
    state = state.copyWith(listOfTodoItems: listToUpdate);
}

Points to note, I directly used Equatable for this, but this could've been done by overriding == operator and hashCode methods.

I directly used copyWith method, again assuming that you already have this in your data class or generated using freezed package.

I used a simple sample of ToDoListItem but this idea applies to most of these examples. A more complex situation would be working with nested lists within nested lists in data classes.

This way you're only removing data within state management boundaries and you're not sending/receiving updated ToDoItem's to/from the backend.

答案2

得分: 1

以下是代码的翻译部分:

没有以任何方式测试这段代码,我认为这可能可以解决问题。

问题出在API调用中的错误处理以及它对将新过滤状态分配给您的实际“AsyncNotifier”状态的影响。为此,您可能需要使用try/catch包装API调用或根据服务器响应采取行动。

```dart
  Future<void> removeTodo(String todoId) async {
    // 我们需要原始状态,但要删除todo。
    final filteredState = [
      for (final todo in state.requireValue)
        if (todo.id != todoId) todo,
    ];

    // 现在在本地变量中保存了正确的新状态,将加载值设置为状态。
    state = const AsyncValue.loading();

    // 执行删除操作
    await http.delete('api/todos/$todoId');

    // 将新过滤状态的值设置为我们的状态
    // 注意:由于状态的不可变性,我们确实需要使用分配操作来分配这个值。
    state = AsyncValue.data(filteredState);
  }
英文:

Without having tested this code in any way, I think this might be able to do the trick.

The issue is with error handling in the API call and how that will influence the assigning of the new filtered state to your actual AsyncNotifier state. For that you might want to wrap the API call with a try/catch or take action depending on the response from your server.

  Future&lt;void&gt; removeTodo(String todoId) async {
    // We need the original state, but with the todo removed.
    final filteredState = [
      for (final todo in state.requireValue)
        if (todo.id != todoId) todo,
    ];

    // With the correct new state now saved in a local variable, set the loading value to the state.
    state = const AsyncValue.loading();

    // Perform delete
    await http.delete(&#39;api/todos/$todoId&#39;);

    // Set the value of the new filtered state to our state
    // Note: we do need to assign this value with an assign action, because of the immutable nature of the state.
    state = AsyncValue.data(filteredState);
  }

答案3

得分: 0

我可以假设您可以通过知道对象的个人标识符,快速搜索并在数组(映射)中更改/删除对象。您可以远程删除对象,还可以手动更改本地状态,而无需再次获取列表。在这种情况下,您可能需要传递一个额外的参数 bool notifyListeners = false,以便在远程ISP更改时(如果远程ISP依赖于本地ISP),无需重新构建ISP。尽管这一切看起来相当奇怪。

英文:

I can assume that you can quickly search and change/delete an object in an array (Map) by knowing its individual id. You can delete an object remotely and also change the local state manually without getting the list again. You may have to pass an additional parameter bool notifyListeners = false in this case, so that you do not have to rebuild the ISP when the remote ISP changes (if the remote one depends on the local one). It all looks pretty strange, though.

huangapple
  • 本文由 发表于 2023年3月10日 01:32:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75688130.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定