Vinci
Vinci

Reputation: 1530

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 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.

Upvotes: 4

Views: 3586

Answers (3)

Ruble
Ruble

Reputation: 4844

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 provider when the remote provider changes (if the remote one depends on the local one). It all looks pretty strange, though.

Upvotes: 0

bqubique
bqubique

Reputation: 1405

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<ToDoItem> 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're searching the list for
    final indexOfSearchItem = listOfToDoItems.indexWhere((currentToDoItem)=> currentToDoItem == ToDoItem(toDoValue: 'Math class'));

    //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're going to update
    listToUpdate.removeAt(indexOfSearchItem);

    //Step 4: update the state'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.

Upvotes: 2

Robin
Robin

Reputation: 138

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<void> 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('api/todos/$todoId');

    // 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);
  }

Upvotes: 1

Related Questions