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