3

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.

Vinci
  • 1,382
  • 10
  • 12

3 Answers3

1

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);
  }
Robin
  • 58
  • 6
  • Is setting the AsyncValue state to loading mandatory? My (limited) understanding so far is that potential listeners would get notified upon state value changes anyhow. – Vinci Mar 09 '23 at 21:36
  • 1
    I don't think it is mandatory (also just starting out with Riverpod 2.0 here). It totally depends on your UX situation whether setting the loading state is beneficial for a user of the app. – Robin Mar 09 '23 at 21:40
  • Thank you, I think that answers my question. I've also not known about the (currently undocumented?) method "requireValue" which helps dealing with all the null-safety. – Vinci Mar 10 '23 at 10:12
  • 1
    It is good to know that the `requireValue` is a bit dangerous when you are unsure whether the value is indeed there, then it might throw a `StateError` (see the docs on the `requireValue` getter within the `AsyncValueX` class inside ) – Robin Mar 10 '23 at 15:06
1

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.

bqubique
  • 678
  • 6
  • 17
  • Sorry I should have mentioned that I'd like to remove the item from the server as well as from internal state. I'd simply like to avoid re-fetching the whole collection. – Vinci Mar 09 '23 at 21:31
  • Just send the ToDoItem to the server then and let the backend deal with it. Then, whenever the app is reloaded it gets the list of todo items again and then when things get removed along the way you decide when exactly you want to update the items in the backend. – bqubique Mar 09 '23 at 21:38
0

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.

Ruble
  • 2,589
  • 3
  • 6
  • 29