From 6109c96255d62052e12275a033b3039c299526a5 Mon Sep 17 00:00:00 2001
From: Adrien Bonnin <adbonnin@ippon.fr>
Date: Fri, 16 Sep 2022 09:17:34 +0200
Subject: [PATCH] =?UTF-8?q?:sparkles:=20Ajout=20des=20exemples=20pour=20la?=
 =?UTF-8?q?=20pr=C3=A9sentation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/example.dart                              |  20 +-
 lib/main.dart                                 | 185 +++++++++---
 lib/presentation/01_introduction_example.dart |  99 +++++++
 lib/presentation/02_provider_example.dart     | 106 +++++++
 lib/presentation/03_select_example.dart       | 113 ++++++++
 lib/presentation/04_stateful_example.dart     | 161 +++++++++++
 .../05_state_provider_example.dart            | 172 +++++++++++
 .../06_state_notifier_provider.dart           | 211 ++++++++++++++
 lib/presentation/07_overrides_example.dart    | 213 ++++++++++++++
 .../08_future_provider_example.dart           | 255 ++++++++++++++++
 lib/presentation/09_async_value_example.dart  | 271 ++++++++++++++++++
 .../smaller_stateful_example.dart             |  55 ----
 lib/toto.md                                   |   0
 lib/widgets/space.dart                        |  16 ++
 pubspec.lock                                  |   7 +
 pubspec.yaml                                  |   1 +
 test/presentation/listener_test.dart          |  27 --
 17 files changed, 1788 insertions(+), 124 deletions(-)
 create mode 100644 lib/presentation/01_introduction_example.dart
 create mode 100644 lib/presentation/02_provider_example.dart
 create mode 100644 lib/presentation/03_select_example.dart
 create mode 100644 lib/presentation/04_stateful_example.dart
 create mode 100644 lib/presentation/05_state_provider_example.dart
 create mode 100644 lib/presentation/06_state_notifier_provider.dart
 create mode 100644 lib/presentation/07_overrides_example.dart
 create mode 100644 lib/presentation/08_future_provider_example.dart
 create mode 100644 lib/presentation/09_async_value_example.dart
 delete mode 100644 lib/presentation/smaller_stateful_example.dart
 create mode 100644 lib/toto.md
 delete mode 100644 test/presentation/listener_test.dart

diff --git a/lib/example.dart b/lib/example.dart
index 85c4d23..0e6c382 100644
--- a/lib/example.dart
+++ b/lib/example.dart
@@ -1,17 +1,29 @@
 import 'package:flutter/material.dart';
 
+class ExampleGroup {
+  const ExampleGroup({
+    required this.title,
+    required this.examples,
+  });
+
+  final String title;
+  final List<Example> examples;
+}
+
 class Example {
   const Example({
     required this.title,
     required this.icon,
     required this.path,
     required this.builder,
+    this.isScrollable = true,
   });
 
   final String title;
   final IconData icon;
   final String path;
   final WidgetBuilder builder;
+  final bool isScrollable;
 }
 
 class ExampleContainer extends StatelessWidget {
@@ -24,14 +36,16 @@ class ExampleContainer extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final child = example.builder(context);
+
     return Scaffold(
       appBar: AppBar(
         title: Text(example.title),
       ),
       body: Center(
-        child: SingleChildScrollView(
-          child: example.builder(context),
-        ),
+        child: example.isScrollable
+            ? SingleChildScrollView(child: child) //
+            : child,
       ),
     );
   }
diff --git a/lib/main.dart b/lib/main.dart
index 9445485..68b8034 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,42 +1,110 @@
-import 'package:article_flutter_riverpod/example.dart';
 import 'package:article_flutter_riverpod/article_02/consumer_example.dart';
 import 'package:article_flutter_riverpod/article_02/parent_example.dart';
 import 'package:article_flutter_riverpod/article_02/stateful_example.dart';
 import 'package:article_flutter_riverpod/article_02/stateless_example.dart';
-import 'package:article_flutter_riverpod/presentation/smaller_stateful_example.dart';
+import 'package:article_flutter_riverpod/example.dart';
+import 'package:article_flutter_riverpod/presentation/01_introduction_example.dart' as introduction_example;
+import 'package:article_flutter_riverpod/presentation/02_provider_example.dart' as provider_example;
+import 'package:article_flutter_riverpod/presentation/03_select_example.dart' as select_example;
+import 'package:article_flutter_riverpod/presentation/04_stateful_example.dart' as stateful_example;
+import 'package:article_flutter_riverpod/presentation/05_state_provider_example.dart' as state_provider_example;
+import 'package:article_flutter_riverpod/presentation/07_overrides_example.dart' as overrides_example;
+import 'package:article_flutter_riverpod/presentation/08_future_provider_example.dart' as future_provider_example;
+import 'package:article_flutter_riverpod/presentation/09_async_value_example.dart' as async_value_example;
 import 'package:flutter/material.dart';
 import 'package:go_router/go_router.dart';
+import 'package:sticky_headers/sticky_headers.dart';
 
 final examples = [
-  Example(
-    title: 'Consumer',
-    icon: Icons.eco,
-    path: 'consumer',
-    builder: (_) => const ConsumerExample(),
-  ),
-  Example(
-    title: 'Stateless',
-    icon: Icons.remove_circle_outline,
-    path: 'stateless',
-    builder: (_) => const StatelessExample(),
-  ),
-  Example(
-    title: 'Stateful',
-    icon: Icons.add_circle_outline,
-    path: 'stateful',
-    builder: (_) => const StatefulExample(),
-  ),
-  Example(
-    title: 'Stateful (smaller)',
-    icon: Icons.add_circle_outline,
-    path: 'smaller-stateful',
-    builder: (_) => const SmallerStatefulExample(),
+  ExampleGroup(
+    title: 'Article 01',
+    examples: [
+      Example(
+        title: 'Consumer',
+        icon: Icons.eco,
+        path: 'consumer',
+        builder: (_) => const ConsumerExample(),
+      ),
+      Example(
+        title: 'Stateless',
+        icon: Icons.remove_circle_outline,
+        path: 'stateless',
+        builder: (_) => const StatelessExample(),
+      ),
+      Example(
+        title: 'Stateful',
+        icon: Icons.add_circle_outline,
+        path: 'stateful',
+        builder: (_) => const StatefulExample(),
+      ),
+      Example(
+        title: 'Parent ProviderScope',
+        icon: Icons.child_care,
+        path: 'parent-providerscope',
+        builder: (_) => const ParentExample(),
+      ),
+    ],
   ),
-  Example(
-    title: 'Parent ProviderScope',
-    icon: Icons.child_care,
-    path: 'parent-providerscope',
-    builder: (_) => const ParentExample(),
+  ExampleGroup(
+    title: 'Presentation',
+    examples: [
+      Example(
+        title: 'Introduction',
+        icon: Icons.eco,
+        path: 'todo-introduction',
+        builder: (_) => const introduction_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'Provider',
+        icon: Icons.arrow_forward,
+        path: 'todo-provider',
+        builder: (_) => const provider_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'Select',
+        icon: Icons.filter_alt,
+        path: 'todo-select',
+        builder: (_) => const select_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'Stateful',
+        icon: Icons.add_circle_outline,
+        path: 'todo-stateful',
+        builder: (_) => const stateful_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'StateProvider',
+        icon: Icons.folder_open,
+        path: 'todo-state-provider',
+        builder: (_) => const state_provider_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'Overrides',
+        icon: Icons.filter,
+        path: 'todo-overrides',
+        builder: (_) => const overrides_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'FutureProvider',
+        icon: Icons.refresh,
+        path: 'todo-future-provider',
+        builder: (_) => const future_provider_example.TodoExample(),
+        isScrollable: false,
+      ),
+      Example(
+        title: 'AsyncValue',
+        icon: Icons.update,
+        path: 'todo-async-value',
+        builder: (_) => const async_value_example.TodoAsyncValueExample(),
+        isScrollable: false,
+      ),
+    ],
   ),
 ];
 
@@ -52,11 +120,12 @@ class ArticleApp extends StatelessWidget {
       path: '/',
       builder: (_, __) => const HomePage(),
       routes: [
-        for (Example example in examples) //
-          GoRoute(
-            path: example.path,
-            builder: (_, __) => ExampleContainer(example),
-          )
+        for (ExampleGroup exampleTitle in examples) //
+          for (Example example in exampleTitle.examples) //
+            GoRoute(
+              path: example.path,
+              builder: (_, __) => ExampleContainer(example),
+            )
       ],
     ),
   ]);
@@ -85,21 +154,59 @@ class HomePage extends StatelessWidget {
         title: const Text('Flutter par la pratique'),
       ),
       body: ListView.builder(
-        itemBuilder: _buildItem,
+        itemBuilder: (_, index) => ExampleGroupListItem(examples[index]),
         itemCount: examples.length,
       ),
     );
   }
+}
+
+class ExampleGroupListItem extends StatelessWidget {
+  const ExampleGroupListItem(this.exampleGroup, {Key? key}) : super(key: key);
+
+  final ExampleGroup exampleGroup;
+
+  @override
+  Widget build(BuildContext context) {
+    return StickyHeader(
+      header: Container(
+        height: 50,
+        color: Colors.white,
+        alignment: Alignment.centerLeft,
+        padding: const EdgeInsets.symmetric(horizontal: 16),
+        child: Text(
+          exampleGroup.title,
+          overflow: TextOverflow.ellipsis,
+          style: Theme.of(context).textTheme.subtitle2,
+        ),
+      ),
+      content: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          for (Example example in exampleGroup.examples) //
+            ExampleListItem(example)
+        ],
+      ),
+    );
+  }
+}
 
-  Widget _buildItem(BuildContext context, int index) {
-    final example = examples[index];
+class ExampleListItem extends StatelessWidget {
+  const ExampleListItem(this.example, {Key? key}) : super(key: key);
 
+  final Example example;
+
+  @override
+  Widget build(BuildContext context) {
     return ListTile(
       leading: Icon(
         example.icon,
         color: Theme.of(context).primaryColor,
       ),
-      title: Text(example.title),
+      title: Text(
+        example.title,
+        overflow: TextOverflow.ellipsis,
+      ),
       trailing: const Icon(Icons.chevron_right),
       onTap: () => context.push('/${example.path}'),
     );
diff --git a/lib/presentation/01_introduction_example.dart b/lib/presentation/01_introduction_example.dart
new file mode 100644
index 0000000..49cb549
--- /dev/null
+++ b/lib/presentation/01_introduction_example.dart
@@ -0,0 +1,99 @@
+import 'package:flutter/material.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const TodoList();
+  }
+}
+
+class TodoList extends StatelessWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView.builder(
+      itemBuilder: (_, index) => TodoListItem(
+        todos[index],
+      ),
+      itemCount: todos.length,
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+  }) : super(key: key);
+
+  final Todo todo;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: null,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
diff --git a/lib/presentation/02_provider_example.dart b/lib/presentation/02_provider_example.dart
new file mode 100644
index 0000000..031c193
--- /dev/null
+++ b/lib/presentation/02_provider_example.dart
@@ -0,0 +1,106 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final todos = ref.watch(todosProvider);
+
+    return ListView.builder(
+      itemBuilder: (_, index) => TodoListItem(
+        todos[index],
+      ),
+      itemCount: todos.length,
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+  }) : super(key: key);
+
+  final Todo todo;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: null,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = Provider<List<Todo>>((ref) => todos);
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
diff --git a/lib/presentation/03_select_example.dart b/lib/presentation/03_select_example.dart
new file mode 100644
index 0000000..47f19a2
--- /dev/null
+++ b/lib/presentation/03_select_example.dart
@@ -0,0 +1,113 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  static const _isUncheckedFilter = true;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+
+    return ListView.builder(
+      itemBuilder: (_, index) => TodoListItem(
+        todos[index],
+      ),
+      itemCount: todos.length,
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+  }) : super(key: key);
+
+  final Todo todo;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: null,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = Provider<List<Todo>>((ref) => todos);
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+}
diff --git a/lib/presentation/04_stateful_example.dart b/lib/presentation/04_stateful_example.dart
new file mode 100644
index 0000000..13591c7
--- /dev/null
+++ b/lib/presentation/04_stateful_example.dart
@@ -0,0 +1,161 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (_, index) => TodoListItem(
+              todos[index],
+            ),
+            itemCount: todos.length,
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+  }) : super(key: key);
+
+  final Todo todo;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: null,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = Provider<List<Todo>>((ref) => todos);
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+}
diff --git a/lib/presentation/05_state_provider_example.dart b/lib/presentation/05_state_provider_example.dart
new file mode 100644
index 0000000..efd9cf7
--- /dev/null
+++ b/lib/presentation/05_state_provider_example.dart
@@ -0,0 +1,172 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (_, index) => TodoListItem(
+              todos[index],
+              onCheckedChanged: (value) => _onCheckedChanged(todos[index], value),
+            ),
+            itemCount: todos.length,
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+
+  void _onCheckedChanged(Todo todo, bool? value) {
+    final updatedTodo = todo.copyWith(checked: value != null && value);
+    ref.read(todosProvider.notifier).state = ref.read(todosProvider.notifier).state.copyWithTodo(updatedTodo);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+    this.onCheckedChanged,
+  }) : super(key: key);
+
+  final Todo todo;
+  final ValueChanged<bool?>? onCheckedChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: onCheckedChanged,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = StateProvider<List<Todo>>((ref) => todos);
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+
+  List<Todo> copyWithTodo(Todo updatedTodo) => //
+      [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo];
+}
diff --git a/lib/presentation/06_state_notifier_provider.dart b/lib/presentation/06_state_notifier_provider.dart
new file mode 100644
index 0000000..d3300d9
--- /dev/null
+++ b/lib/presentation/06_state_notifier_provider.dart
@@ -0,0 +1,211 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (_, index) => TodoListItem(
+              todos[index],
+              onCheckedChanged: (value) => _onCheckedChanged(todos[index], value),
+            ),
+            itemCount: todos.length,
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+
+  void _onCheckedChanged(Todo todo, bool? value) {
+    final updatedTodo = todo.copyWith(checked: value != null && value);
+    ref.read(todosProvider.notifier).updateTodo(updatedTodo);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+    this.onCheckedChanged,
+  }) : super(key: key);
+
+  final Todo todo;
+  final ValueChanged<bool?>? onCheckedChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: onCheckedChanged,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = StateNotifierProvider<TodosStateNotifier, List<Todo>>(//
+    (ref) => TodosStateNotifier(todos));
+
+class TodosStateNotifier extends StateNotifier<List<Todo>> {
+  TodosStateNotifier(List<Todo> todos) : super(todos);
+
+  void updateTodo(Todo updatedTodo) {
+    state = state.copyWithTodo(updatedTodo);
+  }
+}
+
+class TodosChangeNotifier extends ChangeNotifier {
+  TodosChangeNotifier(this.todos);
+
+  List<Todo> todos;
+
+  void updateTodo(Todo updatedTodo) {
+    todos = todos.copyWithTodo(updatedTodo);
+    notifyListeners();
+  }
+}
+
+class MutableTodosChangeNotifier extends ChangeNotifier {
+  MutableTodosChangeNotifier(this.todos);
+
+  final List<Todo> todos;
+
+  void updateTodo(Todo updatedTodo) {
+    todos.updateTodo(updatedTodo);
+    notifyListeners();
+  }
+}
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+
+  List<Todo> copyWithTodo(Todo updatedTodo) => //
+      [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo];
+
+  void updateTodo(Todo updatedTodo) {
+    for (int i = 0; i < length; i++) {
+      if (this[i].id == updatedTodo.id) {
+        this[i] = updatedTodo;
+      }
+    }
+  }
+}
diff --git a/lib/presentation/07_overrides_example.dart b/lib/presentation/07_overrides_example.dart
new file mode 100644
index 0000000..f554346
--- /dev/null
+++ b/lib/presentation/07_overrides_example.dart
@@ -0,0 +1,213 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (_, index) => TodoListItem(
+              todos[index],
+              onCheckedChanged: (value) => _onCheckedChanged(todos[index], value),
+            ),
+            itemCount: todos.length,
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+
+  void _onCheckedChanged(Todo todo, bool? value) {
+    final updatedTodo = todo.copyWith(checked: value != null && value);
+    ref.read(todosProvider.notifier).updateTodo(updatedTodo);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+    this.onCheckedChanged,
+  }) : super(key: key);
+
+  final Todo todo;
+  final ValueChanged<bool?>? onCheckedChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: onCheckedChanged,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = StateNotifierProvider<TodosStateNotifier, List<Todo>>(//
+    (ref) => TodosStateNotifier(ref.watch(todosRepositoryProvider)));
+
+class TodosStateNotifier extends StateNotifier<List<Todo>> {
+  TodosStateNotifier(this.todosRepository) : super([]) {
+    load();
+  }
+
+  final TodosRepository todosRepository;
+
+  void load() {
+    state = todosRepository.getTodos();
+  }
+
+  void updateTodo(Todo updatedTodo) {
+    state = todosRepository.updateTodos(updatedTodo);
+  }
+}
+
+final todosRepositoryProvider = Provider<TodosRepository>((ref) => FakeTodosRepository(todos));
+
+abstract class TodosRepository {
+  List<Todo> getTodos();
+
+  List<Todo> updateTodos(Todo updatedTodo);
+}
+
+class FakeTodosRepository implements TodosRepository {
+  FakeTodosRepository(this.todos);
+
+  List<Todo> todos;
+
+  @override
+  List<Todo> getTodos() {
+    return todos;
+  }
+
+  @override
+  List<Todo> updateTodos(Todo updatedTodo) {
+    return todos = todos.copyWithTodo(updatedTodo);
+  }
+}
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+
+  List<Todo> copyWithTodo(Todo updatedTodo) => //
+      [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo];
+}
diff --git a/lib/presentation/08_future_provider_example.dart b/lib/presentation/08_future_provider_example.dart
new file mode 100644
index 0000000..f4766ed
--- /dev/null
+++ b/lib/presentation/08_future_provider_example.dart
@@ -0,0 +1,255 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoExample extends StatelessWidget {
+  const TodoExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter)));
+    final asyncTodosCount = ref.watch(todosCountProvider);
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Padding(
+          padding: const EdgeInsets.only(top: 16, bottom: 8),
+          child: TodoListHeader(asyncTodosCount),
+        ),
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (_, index) => TodoListItem(
+              todos[index],
+              onCheckedChanged: (value) => _onCheckedChanged(todos[index], value),
+            ),
+            itemCount: todos.length,
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+
+  void _onCheckedChanged(Todo todo, bool? value) {
+    final updatedTodo = todo.copyWith(checked: value != null && value);
+    ref.read(todosProvider.notifier).updateTodo(updatedTodo);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListHeader extends StatelessWidget {
+  const TodoListHeader(
+    this.todosCount, {
+    Key? key,
+  }) : super(key: key);
+
+  final AsyncValue<int> todosCount;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 16,
+      alignment: Alignment.center,
+      child: todosCount.when(
+        data: (count) => Text(
+          "$count todos",
+          style: Theme.of(context).textTheme.subtitle2,
+        ),
+        error: (error, _) => Text(error.toString()),
+        loading: () => const AspectRatio(
+          aspectRatio: 1,
+          child: CircularProgressIndicator(),
+        ),
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+    this.onCheckedChanged,
+  }) : super(key: key);
+
+  final Todo todo;
+  final ValueChanged<bool?>? onCheckedChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: onCheckedChanged,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = StateNotifierProvider<TodosStateNotifier, List<Todo>>(//
+    (ref) => TodosStateNotifier(ref.watch(todosRepositoryProvider)));
+
+class TodosStateNotifier extends StateNotifier<List<Todo>> {
+  TodosStateNotifier(this.todosRepository) : super([]) {
+    load();
+  }
+
+  final TodosRepository todosRepository;
+
+  void load() {
+    state = todosRepository.getTodos();
+  }
+
+  void updateTodo(Todo updatedTodo) {
+    state = todosRepository.updateTodos(updatedTodo);
+  }
+}
+
+final todosRepositoryProvider = Provider<TodosRepository>((ref) => FakeTodosRepository(todos));
+
+final todosCountProvider = FutureProvider<int>((ref) => ref.watch(todosRepositoryProvider).getTodosCount());
+
+abstract class TodosRepository {
+  List<Todo> getTodos();
+
+  List<Todo> updateTodos(Todo updatedTodo);
+
+  Future<int> getTodosCount();
+}
+
+class FakeTodosRepository implements TodosRepository {
+  FakeTodosRepository(this.todos);
+
+  List<Todo> todos;
+
+  @override
+  List<Todo> getTodos() {
+    return todos;
+  }
+
+  @override
+  List<Todo> updateTodos(Todo updatedTodo) {
+    return todos = todos.copyWithTodo(updatedTodo);
+  }
+
+  @override
+  Future<int> getTodosCount() {
+    return Future.delayed(const Duration(seconds: 3), () => todos.length);
+  }
+}
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+
+  List<Todo> copyWithTodo(Todo updatedTodo) => //
+      [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo];
+}
diff --git a/lib/presentation/09_async_value_example.dart b/lib/presentation/09_async_value_example.dart
new file mode 100644
index 0000000..3254c29
--- /dev/null
+++ b/lib/presentation/09_async_value_example.dart
@@ -0,0 +1,271 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class TodoAsyncValueExample extends StatelessWidget {
+  const TodoAsyncValueExample({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ProviderScope(
+      child: TodoList(),
+    );
+  }
+}
+
+class TodoList extends ConsumerStatefulWidget {
+  const TodoList({Key? key}) : super(key: key);
+
+  @override
+  ConsumerState<TodoList> createState() => _TodoListState();
+}
+
+class _TodoListState extends ConsumerState<TodoList> {
+  bool _isUncheckedFilter = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final asyncTodos = ref.watch(todosProvider.selectTodos(_isUncheckedFilter));
+    final asyncTodosCount = ref.watch(todosCountProvider);
+
+    return Column(
+      children: [
+        TodoListFilter(
+          filter: _isUncheckedFilter,
+          onFilterChanged: _onFilterChanged,
+        ),
+        const Divider(height: 1),
+        Padding(
+          padding: const EdgeInsets.only(top: 16, bottom: 8),
+          child: TodoListHeader(asyncTodosCount),
+        ),
+        Expanded(
+          child: asyncTodos.when(
+            data: (todos) => ListView.builder(
+              itemBuilder: (_, index) => TodoListItem(
+                todos[index],
+                onCheckedChanged: (value) => _onCheckedChanged(todos[index], value),
+              ),
+              itemCount: todos.length,
+            ),
+            error: (error, _) => Center(
+              child: Text(error.toString()),
+            ),
+            loading: () => const Center(
+              child: CircularProgressIndicator(),
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  void _onFilterChanged(bool value) {
+    setState(() => _isUncheckedFilter = value);
+  }
+
+  void _onCheckedChanged(Todo todo, bool? value) {
+    final updatedTodo = todo.copyWith(checked: value != null && value);
+    ref.read(todosProvider.notifier).updateTodo(updatedTodo);
+  }
+}
+
+class TodoListFilter extends StatelessWidget {
+  const TodoListFilter({
+    Key? key,
+    required this.filter,
+    required this.onFilterChanged,
+  }) : super(key: key);
+
+  final bool filter;
+  final ValueChanged<bool> onFilterChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text("Unchecked"),
+          Switch(
+            value: filter,
+            onChanged: onFilterChanged,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class TodoListHeader extends StatelessWidget {
+  const TodoListHeader(
+    this.todosCount, {
+    Key? key,
+  }) : super(key: key);
+
+  final AsyncValue<int> todosCount;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 16,
+      alignment: Alignment.center,
+      child: todosCount.when(
+        data: (count) => Text(
+          "$count todos",
+          style: Theme.of(context).textTheme.subtitle2,
+        ),
+        error: (error, _) => Text(error.toString()),
+        loading: () => const AspectRatio(
+          aspectRatio: 1,
+          child: CircularProgressIndicator(),
+        ),
+      ),
+    );
+  }
+}
+
+class TodoListItem extends StatelessWidget {
+  const TodoListItem(
+    this.todo, {
+    Key? key,
+    this.onCheckedChanged,
+  }) : super(key: key);
+
+  final Todo todo;
+  final ValueChanged<bool?>? onCheckedChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return CheckboxListTile(
+      value: todo.checked,
+      onChanged: onCheckedChanged,
+      title: Text(
+        todo.title,
+        overflow: TextOverflow.ellipsis,
+      ),
+      controlAffinity: ListTileControlAffinity.leading,
+    );
+  }
+}
+
+final todosProvider = StateNotifierProvider<TodosStateNotifier, AsyncValue<List<Todo>>>(//
+    (ref) => TodosStateNotifier(ref.watch(todosRepositoryProvider)));
+
+class TodosStateNotifier extends StateNotifier<AsyncValue<List<Todo>>> {
+  TodosStateNotifier(this.todosRepository) : super(const AsyncValue.loading()) {
+    load();
+  }
+
+  final TodosRepository todosRepository;
+
+  void load() async {
+    state = await AsyncValue.guard(() => todosRepository.getTodos());
+  }
+
+  void updateTodo(Todo updatedTodo) async {
+    state = const AsyncValue.loading();
+    state = await AsyncValue.guard(() => todosRepository.updateTodos(updatedTodo));
+  }
+}
+
+final todosRepositoryProvider = Provider<TodosRepository>((ref) => FakeTodosRepository(todos));
+
+final todosCountProvider = FutureProvider<int>((ref) => ref.watch(todosRepositoryProvider).getTodosCount());
+
+abstract class TodosRepository {
+  Future<List<Todo>> getTodos();
+
+  Future<List<Todo>> updateTodos(Todo updatedTodo);
+
+  Future<int> getTodosCount();
+}
+
+class FakeTodosRepository implements TodosRepository {
+  FakeTodosRepository(this.todos);
+
+  List<Todo> todos;
+
+  @override
+  Future<List<Todo>> getTodos() {
+    return Future.delayed(const Duration(seconds: 5), () => todos);
+  }
+
+  @override
+  Future<List<Todo>> updateTodos(Todo updatedTodo) {
+    return Future.delayed(const Duration(seconds: 1), () => todos = todos.copyWithTodo(updatedTodo));
+  }
+
+  @override
+  Future<int> getTodosCount() {
+    return Future.delayed(const Duration(seconds: 3), () => todos.length);
+  }
+}
+
+const todos = [
+  Todo(
+    id: 0,
+    title: 'Unit test passed',
+    checked: true,
+  ),
+  Todo(
+    id: 1,
+    title: 'Code reviewed',
+    checked: true,
+  ),
+  Todo(
+    id: 2,
+    title: 'Acceptance criteria for each issue met',
+  ),
+  Todo(
+    id: 3,
+    title: 'Functional tests passed',
+  ),
+  Todo(
+    id: 4,
+    title: 'Non-functional requirements met',
+  ),
+  Todo(
+    id: 5,
+    title: 'Product owner accepts the User Story',
+  ),
+];
+
+class Todo {
+  const Todo({
+    required this.id,
+    required this.title,
+    this.checked = false,
+  });
+
+  final int id;
+  final String title;
+  final bool checked;
+
+  Todo copyWith({
+    int? id,
+    String? title,
+    bool? checked,
+  }) {
+    return Todo(
+      id: id ?? this.id,
+      title: title ?? this.title,
+      checked: checked ?? this.checked,
+    );
+  }
+}
+
+extension TodoListExtension on List<Todo> {
+  List<Todo> whereTodos(bool isUnchecked) => //
+      where((todo) => !isUnchecked || !todo.checked).toList();
+
+  List<Todo> copyWithTodo(Todo updatedTodo) => //
+      [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo];
+}
+
+extension TodoStateNotifierProviderExtension on StateNotifierProvider<TodosStateNotifier, AsyncValue<List<Todo>>> {
+  AlwaysAliveProviderListenable<AsyncValue<List<Todo>>> selectTodos(bool isUncheckedFilter) => //
+      select((async) => async
+          .unwrapPrevious() //
+          .whenData((value) => value.whereTodos(isUncheckedFilter)));
+}
diff --git a/lib/presentation/smaller_stateful_example.dart b/lib/presentation/smaller_stateful_example.dart
deleted file mode 100644
index a6d4217..0000000
--- a/lib/presentation/smaller_stateful_example.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-final countServiceProvider = StateNotifierProvider<CountService, int>((ref) => CountService(42)); // <1>
-
-class CountService extends StateNotifier<int> {
-  CountService(int firstValue) : super(firstValue);
-
-  void increment() {
-    state++;
-  }
-}
-
-class SmallerStatefulExample extends StatelessWidget {
-  const SmallerStatefulExample({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return const ProviderScope(
-      child: Counter(),
-    );
-  }
-}
-
-class Counter extends ConsumerStatefulWidget { // <2>
-  const Counter({Key? key}) : super(key: key);
-
-  @override
-  ConsumerState<Counter> createState() => _CounterState(); // <3>
-}
-
-class _CounterState extends ConsumerState<Counter> { // <4>
-  late int firstValue;
-
-  @override
-  void initState() {
-    super.initState();
-    firstValue = ref.read(countServiceProvider); // <5>
-  }
-
-  @override
-  Widget build(BuildContext context) { // <6>
-    final currentValue = ref.watch(countServiceProvider); // <7>
-
-    return Column(
-      mainAxisSize: MainAxisSize.min,
-      children: [
-        ElevatedButton( // <8>
-          onPressed: () => ref.read(countServiceProvider.notifier).increment(), // <9>
-          child: Text("Increment $firstValue -> $currentValue"),
-        ),
-      ],
-    );
-  }
-}
diff --git a/lib/toto.md b/lib/toto.md
new file mode 100644
index 0000000..e69de29
diff --git a/lib/widgets/space.dart b/lib/widgets/space.dart
index cb311a0..a7fa56b 100644
--- a/lib/widgets/space.dart
+++ b/lib/widgets/space.dart
@@ -16,3 +16,19 @@ class HSpace extends StatelessWidget {
     return SizedBox(height: size);
   }
 }
+
+class VSpace extends StatelessWidget {
+  const VSpace(this.size, {Key? key}) : super(key: key);
+
+  static const s = HSpace(Insets.s);
+  static const m = HSpace(Insets.m);
+  static const l = HSpace(Insets.l);
+  static const xl = HSpace(Insets.xl);
+
+  final double size;
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(width: size);
+  }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 3ff2c09..2e4e015 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -175,6 +175,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.7.2+1"
+  sticky_headers:
+    dependency: "direct main"
+    description:
+      name: sticky_headers
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.0+2"
   stream_channel:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index d22dffd..3437f3b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -36,6 +36,7 @@ dependencies:
   cupertino_icons: ^1.0.2
   flutter_riverpod: ^2.0.0-dev.9
   go_router: ^4.3.0
+  sticky_headers: ^0.3.0
 
 dev_dependencies:
   flutter_test:
diff --git a/test/presentation/listener_test.dart b/test/presentation/listener_test.dart
deleted file mode 100644
index 8caefbb..0000000
--- a/test/presentation/listener_test.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import '../utils.dart';
-
-final intProvider = StateProvider<int>((_) => 13); // <1>
-
-void main() {
-  test('doit écouter un état', () {
-    const defaultValue = -1;
-
-    // given:
-    final container = createContainer();
-
-    var intValue = defaultValue; // <2>
-    container.listen<int>(intProvider, (_, next) => intValue = next);
-
-    // expect:
-    expect(intValue, equals(defaultValue)); // <3>
-
-    // when:
-    container.read(intProvider.notifier).state = 42; // <4>
-
-    // then:
-    expect(intValue, equals(42)); // <5>
-  });
-}
-- 
GitLab