Build a Todo App with Flutter(13)
riverpod is a reactive caching and data-binding framework, we can write business logic in a manner to StatelessWidget.
We’re able to have our network requests to automatically recompute when necessary and make our logic easily reusable/composable/maintainable.
riverpod is easily implement common UI pattern such as “pull to refresh”/”search as we type”, etc. We can use riverpod to lint code and make refactoring.
Now we don’t need to jump between our main.dart and our UI files anymore, riverpod can be used to declare shared state from anywhere, we can place the code of our shared state where it belongs, be it in a separate package or right next to the Widget that needs it.
// A shared state that can be accessed by multiple widgets at the same time.@riverpodclassCountextends_$Count{ @override int build() => 0; void increment() => state++;}// Consumes the shared state and rebuild when it changesclassTitleextendsConsumerWidget{ @override Widget build(BuildContext context, WidgetRef ref) {final count = ref.watch(countProvider);return Text('$count'); }}
We no longer have to sort/filter lists inside the build method or have to resort to advanced cache mechanism.
@riverpodList<Todo> filteredTodos(Ref ref) { // Providers can consume other providers using the "ref" object. // With ref.watch, providers will automatically update if the watched values changes. final List<Todo> todos = ref.watch(todosProvider); final Filter filter = ref.watch(filterProvider); switch (filter) {case Filter.all:return todos;case Filter.completed:return todos.where((todo) => todo.completed).toList();case Filter.uncompleted:return todos.where((todo) => !todo.completed).toList(); }}
First we define some keys for testing.
final addTodoKey = UniqueKey();final activeFilterKey = UniqueKey();final completedFilterKey = UniqueKey();final allFilterKey = UniqueKey();
We can create a TodoList and initialize it with pre-defined values.
We are using NotifierProvider here as a List<Todo> is a complex object, with advanced business logic like how to edit a todo.
final todoListProvider = NotifierProvider<TodoList, List<Todo>>(TodoList.new);
We use enum to define the different ways to filter the list of todos:
enum TodoListFilter { all, active, completed }
Here is the current active filter, we use StateProvider here as there is no fancy logic behind manipulating the value since it’s just enum.
final todoListFilter = StateProvider((_) => TodoListFilter.all);
We use Provider to calculate the number of uncompleted todos, this value is cached, making it performant. Even multiple widgets try to read the number of uncompleted todos, the value will be computed only once (until the todo-list changes).
This will also optimize unneeded rebuilds if the todo-list changes, but the number of uncompleted todos doesn’t (such as when editing a todo).
final uncompletedTodosCount = Provider<int>((ref) {return ref.watch(todoListProvider).where((todo) => !todo.completed).length;});
We defined filteredTodos as the list of todos after applying of todoListFilter, it uses Provider to avoid recomputing the filtered list unless either the filter of or the todo-list updates.
final filteredTodos = Provider<List<Todo>>((ref) {final filter = ref.watch(todoListFilter);final todos = ref.watch(todoListProvider);switch (filter) {case TodoListFilter.completed:return todos.where((todo) => todo.completed).toList();case TodoListFilter.active:return todos.where((todo) => !todo.completed).toList();case TodoListFilter.all:return todos; }});
Todo Model
The Todo model is a read-only decription of a todo-item.
const _uuid = Uuid();@immutableclassTodo{ const Todo({required this.description,required this.id,this.completed = false, }); final String id; final String description; final bool completed; @override String toString() {return'Todo(description: $description, completed: $completed)'; }}
TodoList Model
The TodoList model is an object that controls a list of Todo.
classTodoListextendsNotifier<List<Todo>> { @override List<Todo> build() => [const Todo(id: 'todo-0', description: 'Buy cookies'),const Todo(id: 'todo-1', description: 'Star Riverpod'),const Todo(id: 'todo-2', description: 'Have a walk'), ]; void add(String description) { state = [...state, Todo(id: _uuid.v4(), description: description)]; } void toggle(String id) { state = [for (final todo in state)if (todo.id == id) Todo( id: todo.id, completed: !todo.completed, description: todo.description, )else todo, ]; } void edit({required String id, required String description}) { state = [for (final todo in state)if (todo.id == id) Todo(id: todo.id, completed: todo.completed, description: description)else todo, ]; } void remove(Todo target) { state = state.where((todo) => todo.id != target.id).toList(); }}
夜雨聆风