I use Provider’s selection APIs (Selector) to ensure widgets only rebuild when the specific data they depend on changes.
-
[Completed count selection] lib/views/todo_view.dart:
Selector<AppStateViewModel, int>(selector: (context, vm) => vm.completedTodosCount, ...)- Depends only on a primitive
intderived fromtodos. Only the count Text rebuilds when the computed value changes.
-
[List length selection] lib/views/todo_view.dart:
Selector<AppStateViewModel, int>(selector: (context, vm) => vm.appState.todos.length, ...)- The ListView container rebuilds only when the length changes (add/remove). Toggling a todo’s
completedflag does not change length, so the container does not rebuild.
-
[Per-row selection] lib/views/todo_view.dart:
- Each row is wrapped with
Selector<AppStateViewModel, Todo>(selector: (context, vm) => vm.appState.todos[index], ...) - Only the row whose Todo instance changes rebuilds (e.g., when its
completedflips). - Rows are keyed with
ValueKey(todo.id), preserving identity across insert/remove operations and minimizing row re-mounting.
- Each row is wrapped with
-
[Stateless item + non-listening actions] lib/views/custom_widget/todo_card.dart:
- TodoCard is stateless and receives primitive props (
task,completed). - Actions use
context.read<AppStateViewModel>(), which does not listen, so dispatching actions does not trigger rebuilds.
- TodoCard is stateless and receives primitive props (
-
[Derived, not duplicated state] lib/view_models/app_state_view_model.dart:
completedTodosCountis a getter derived fromtodosinstead of a manually maintained counter, preventing drift and extra rebuilds.
Together, these choices ensure only the smallest necessary subtree rebuilds:
- The count text when the computed count changes.
- The list container only when length changes.
- The specific TodoCard whose data changed.