From 8fa918a1774d4072b3ec1c3d4f8cd6995c579e72 Mon Sep 17 00:00:00 2001 From: Adrien Bonnin <adbonnin@ippon.fr> Date: Thu, 18 Aug 2022 10:01:50 +0200 Subject: [PATCH] :bookmark: Version 1.0 --- README.md | 664 +++++++++++++++++++++-- docs/images/change_notifier_provider.png | Bin 0 -> 2450 bytes docs/images/declaration_provider.png | Bin 0 -> 2910 bytes docs/images/future_provider.png | Bin 0 -> 3192 bytes docs/images/listener.png | Bin 0 -> 9733 bytes docs/images/provider.png | Bin 0 -> 1502 bytes docs/images/schemas.vsdx | Bin 0 -> 33089 bytes docs/images/select.png | Bin 0 -> 5505 bytes docs/images/state_notifier_provider.png | Bin 0 -> 3203 bytes docs/images/state_provider.png | Bin 0 -> 2185 bytes docs/images/stream_provider.png | Bin 0 -> 3878 bytes docs/images/test_overrides.png | Bin 0 -> 3232 bytes docs/images/watch.png | Bin 0 -> 6505 bytes test/change_notifier_provider.dart | 36 ++ test/dependance_cyclique.dart | 19 + test/future_provider.dart | 26 + test/listener.dart | 40 ++ test/provider.dart | 15 + test/select.dart | 36 ++ test/state_notifier_provider.dart | 40 ++ test/state_provider.dart | 22 + test/stream_provider.dart | 38 ++ test/test_overrides.dart | 23 + test/utils.dart | 16 + test/watch.dart | 22 + test/widget_test.dart | 30 - 26 files changed, 940 insertions(+), 87 deletions(-) create mode 100644 docs/images/change_notifier_provider.png create mode 100644 docs/images/declaration_provider.png create mode 100644 docs/images/future_provider.png create mode 100644 docs/images/listener.png create mode 100644 docs/images/provider.png create mode 100644 docs/images/schemas.vsdx create mode 100644 docs/images/select.png create mode 100644 docs/images/state_notifier_provider.png create mode 100644 docs/images/state_provider.png create mode 100644 docs/images/stream_provider.png create mode 100644 docs/images/test_overrides.png create mode 100644 docs/images/watch.png create mode 100644 test/change_notifier_provider.dart create mode 100644 test/dependance_cyclique.dart create mode 100644 test/future_provider.dart create mode 100644 test/listener.dart create mode 100644 test/provider.dart create mode 100644 test/select.dart create mode 100644 test/state_notifier_provider.dart create mode 100644 test/state_provider.dart create mode 100644 test/stream_provider.dart create mode 100644 test/test_overrides.dart create mode 100644 test/utils.dart create mode 100644 test/watch.dart delete mode 100644 test/widget_test.dart diff --git a/README.md b/README.md index f20313d..1cbae49 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,642 @@ -# article_flutter_riverpod +# Découvrir Riverpod par la pratique +Cet article a pour objectif de faire découvrir pas à pas la bibliothèque Riverpod à partir d'exemples d'utilisation. +## Introduction -## Getting started +Riverpod est une bibliothèque de state management pour Flutter. +Le state management, ou gestion d'état en français, a pour responsabilité de mettre à disposition les différents objets qui constituent l'état de l'application. +Le terme _"état"_, employé tout au long de cet article, fait précisément référence à ces états. -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +La principale particularité de Flutter est que tout est widget. +Le state management ne faisant pas exception, de nombreuses bibliothèques sont apparues proposant chacune leurs propres patterns et méthodes de fonctionnement. +Parmi les plus utilisées on peut citer [BLoC](https://pub.dev/packages/bloc), [GetX](https://pub.dev/packages/get), [Provider](https://pub.dev/packages/provider) et aujourd'hui c'est [Riverpod](https://pub.dev/packages/riverpod) qui nous intéresse. -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +Le créateur et principal contributeur de Riverpod, Remi Rousselet, n'en est pas à son premier ballon d'essai. +Reconnu au sein de la communauté Dart et Flutter, il est entre autre l'auteur de [freezed](https://pub.dev/packages/freezed) (un générateur de code pour les data-classes et unions), [flutter_hooks](https://pub.dev/packages/flutter_hooks) (une implémentation des hooks React) et [Provider](https://pub.dev/packages/provider) (un autre state management). +Cette dernière est le point de départ d'une réflexion plus ambitieuse sur le state management qui impliquera une réécriture complète pour aboutir à Riverpod. +Cet article fait référence à la bibliothèque Provider sous ces termes uniquement pour ne pas la confondre avec la classe `Provider` présente dans Riverpod. -## Add your files +## Installation + +Dès son installation, Riverpod se distingue de ses concurrentes par son découpage en plusieurs bibliothèques : + +- [`riverpod`](https://pub.dev/packages/riverpod) contient le code principale, sans aucune adhérence. +- [`riverpod_flutter`](https://pub.dev/packages/flutter_riverpod) contient le code spécifique pour le framework Flutter. +- [`hooks_riverpod`](https://pub.dev/packages/hooks_riverpod) contient le code spécifique pour la bibliothèque [`flutter_books`](https://pub.dev/packages/flutter_hooks). -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +La bibliothèque `flutter_riverpod` sera utilisée pour aborder l'ensemble des fondamentaux de Riverpod. +La dépendance est à ajouter dans le fichier pubspec.yaml : +```yaml +dependencies: + flutter_riverpod: ^1.0.3 ``` -cd existing_repo -git remote add origin https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod.git -git branch -M main -git push -uf origin main + +## Une histoire de Provider + +L'opération la plus élémentaire est de récupérer un état stocké dans un `ProviderContainer` par l'intermédiaire d'un `Provider`. + +Le `ProviderContainer` est un conteneur d'états. +Pour simplifier, c'est une `Map` avec comme clefs les __instances des providers__ et comme valeurs les états correspondants. +Utiliser des instances comme clef corrige la limitation de la bibliothèque Provider qui ne supporte qu'une valeur par classe. + +Le `Provider` est un moyen de __récupérer__ un état présent dans un `ProviderContainer`. +La déclaration d'un provider permet d'indiquer __le type__ et __la valeur d'initialisation__ de l'état auquel il correspond. + + + +> Déclarer un provider permet de __typer__, __initialiser__ et __récupérer__ un état. + +La récupération d'un état est réalisée de la manière suivante : + +[_source : provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/provider.dart) +```dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final intProvider = Provider<int>((_) => 13); // <1> + +void main() { + test('doit récupérer un état', () { + // given: + final container = ProviderContainer(); // <2> + addTearDown(container.dispose); + + // expect: + expect(container.read(intProvider), equals(13)); // <3> + }); +} ``` -## Integrate with your tools + -- [ ] [Set up project integrations](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/settings/integrations) +Le `Provider` est instancié en tant que variable globale et son état est initialisé avec la valeur 13 `<1>`. +L'instance du `ProviderContainer` contient les états `<2>` et permet de les récupérer en passant l'instance du `Provider` en paramètre de sa méthode `read` `<3>`. -## Collaborate with your team +Déclarer le `Provider` en tant que variable globale `<1>` peut sembler une erreur de conception mais il n'en est rien. +Il est avant tout immutable et ne contient pas un état, mais constitue un __moyen de le récupérer__. +La visibilité globale est alors un choix judicieux pour le rendre disponible n'importe où dans le code. -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +Une autre particularité du `Provider` est de résoudre la principale erreur rencontrée par la bibliothèque `Provider`, à savoir le `ProviderNotFoundException`. +Cette erreur est levée quand un état est accédé alors qu'il n'a pas été encore initialisé. +Le provider étant responsable de l'initialisation de l'état, ce dernier sera systématiquement initialisé avant d'être récupéré. -## Test and Deploy +> Le contrat du `Provider` se limite à la récupération d'un état. +> Il est __trés fortement recommandé__ d'utiliser des objets immutables pour définir les états, les modifications internes sont à proscrire et ne seront pas notifiées. -Use the built-in continuous integration in GitLab. +Plus généralement, l'immutabilité des objets échangés est, entre autres, une bonne pratique dans le développement événementiel car elle limite les effets de bord. +La déclaration de ces objets peut se révéler fastidieuse en Dart et c'est ce que propose de simplifier la bibliothèque [freezed](https://pub.dev/packages/freezed). -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +## Test, test, test and test -*** +Étant donné que le `ProviderContainer` contient les états, en instancier un nouveau pour chaque test permet de garantir leur isolation. -# Editing this README +En plus de contenir les états, le `ProviderContainer` gère aussi leurs cycles de vie et sa méthode `dispose` libère ces ressources à la fin des tests. -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. +Pour homogénéiser leurs écritures et éviter d'éventuels oublis de libération de ressources, la déclaration des `ProviderContainer` peut être factorisée comme le fait Riverpod avec [la méthode createContainer](https://github.com/rrousselGit/riverpod/blob/v1.0.4/packages/flutter_riverpod/test/utils.dart#L11). +Cette méthode sera par la suite utilisée dans les prochains tests de cet article. -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +[_source : utils.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/utils.dart) +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; -## Name -Choose a self-explaining name for your project. +ProviderContainer createContainer({ + ProviderContainer? parent, + List<Override> overrides = const [], + List<ProviderObserver>? observers, +}) { + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + addTearDown(container.dispose); + return container; +} +``` -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +La variable `overrides` présente dans cette factory de `ProviderContainer` va se révéler particulièrement utile pour préparer la situation initiale des tests. +La liste d'`Override`s qu'elle contient vient surcharger le comportement du `ProviderContainer`. +Ces `Override`s sont retournés par les méthodes idoines du `Provider`, `overrideWithProvider` pour le remplacer et `overrideWithValue` pour remplacer l'état récupéré. + +[_source : test_overrides.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/test_overrides.dart) +```dart +final intProvider = Provider<int>((_) => 13); // <1> +final otherIntProvider = Provider<int>((_) => 13); + +void main() { + test('doit surcharger le comportement des Provider', () { + // given: + final container = createContainer( + overrides: [ + intProvider.overrideWithProvider(Provider<int>((_) => 42)), // <2> + otherIntProvider.overrideWithValue(42), + ], + ); + + // expect: + expect(container.read(intProvider), 42); // <3> + expect(container.read(otherIntProvider), 42); + }); +} +``` -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +Deux `Providers` sont déclarés pour retourner la valeur 13 `<1>`. +Le comportement du `ProviderContainer` est surchargé pour remplacer le premier `Provider` et pour remplacer la valeur retournée par le second `<2>`. +La valeur récupérée par les deux est maintenant de 42 `<3>`. -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +## Changer d'état avec le StateProvider + +Le `Provider` est un moyen de récupérer un état, mais en aucun cas de le modifier. +Ce comportement a été confié au `StateProvider` de la manière suivante : + +[_source : state_provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/state_provider.dart) +```dart +final intProvider = StateProvider((_) => 13); // <1> + +void main() { + test('doit modifier un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(intProvider), equals(13)); + + // when: + container.read(intProvider.notifier).state = 42; // <2> + + // expect: + expect(container.read(intProvider), equals(42)); // <3> + }); +} +``` + + + +Le `StateProvider` est instancié de la même manière qu'un `Provider`, en initialisant son état avec la valeur 13 `<1>`. +Le notifier du `StateProvider` est récupéré par l'intermédiaire du `ProviderContainer` et son état est remplacé par la valeur 42 `<2>`. +L'état du `StateProvider` a bien été mis à jour avec cette nouvelle valeur `<3>`. + +> Le contrat du `StateProvider` va de la récupération à la modification d'un état. +> Les modifications sont réalisées en affectant un nouvel objet à la variable `state` du `notifier`. +> Chacune de ces modifications est ensuite notifiée aux objets qui le surveillent. + +Ce fonctionnement implique la création d'un nouvel objet pour indiquer un changement d'état et conforte l'utilisation d'objets immutables. + +La mise à jour de l'état par le `StateProvider` met en évidence un pattern récurrent chez Riverpod. +Étant donné que le `ProviderContainer` propose la méthode `read`, on aurait pu s'attendre qu'il propose son pendant, la méthode `write`. +D'un point de vue conceptuel, l'unique interaction que partagent toutes les classes de `Provider` avec le `ProviderContainer` est de récupérer un état, d'où l'unique présence de la méthode read. +Cependant, chaque classe de `Provider` dispose de son propre contrat et c'est par l'intermédiaire de providers additionnels que les comportements sont adaptés. +Dans le cas du `StateProvider`, c'est le provider additionnel `notifier` qui ajoute le changement de valeur. + +## Surveillance des Providers + +En plus de lire l'état d'un `Provider`, il est possible de surveiller ses changements. +Par souci de simplicité, le paramètre `ProviderRef`, présent dans l'initialisation de chaque `Provider`, avait été jusque-là ignoré. +C'est par son intermédiaire qu'un `Provider` peut lire et/ou surveiller d'autres `Provider`s et ainsi former un graphe de dépendances. + +[_source : watch.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/watch.dart) +```dart +final intProvider = StateProvider<int>((_) => 13); // <1> +final readProvider = Provider<int>((ref) => ref.read(intProvider)); +final watchProvider = Provider<int>((ref) => ref.watch(intProvider)); + +void main() { + test('doit surveiller un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(readProvider), equals(13)); // <2> + expect(container.read(watchProvider), equals(13)); + + // when: + container.read(intProvider.notifier).state = 42; // <3> + + // expect: + expect(container.read(readProvider), equals(13)); // <4> + expect(container.read(watchProvider), equals(42)); + }); +} +``` + + + +Le `StateProvider` `intProvider` est initialisé avec la valeur 13, `readProvider` vient lire son état et `watchProvider` le surveiller `<1>`. +Lors de leurs première lecture, l'état des `readProvider` et `watchProvider` sont identiques à celui du `intProvider` `<2>`. +L'état du `intProvider` est modifié avec la valeur 42 `<3>` et seulement `watchProvider` prend en compte ce changement `<4>`. + +Le comportement des méthodes `read` et `watch` est identique lors de la première initialisation, l'état du `intProvider` est récupéré pour être ensuite retourné. +C'est lors de la modification du intProvider `<3>` que les comportements divergent. +Le `readProvider` ne se préoccupe pas de cette nouvelle valeur alors que la `watchProvider` vient appeler de nouveau sa méthode d'initialisation `<1>` pour mettre à jour son état en adéquation avec celui du `intProvider`. + +> Le `ProviderRef` peut être concidéré comme une façade au `ProviderContainer`. +> Le comportement attendu de ses méthodes `read` et `watch` est identique selon que l'on l'utilise `ProviderContainer` dans le corps du test ou `ProviderRef` dans l'initialisation du `Provider`. + +Ce fonctionnement est illustré par le code ci-dessous tiré de Riverpod : + +[_source : provider_base.dart_](https://github.com/rrousselGit/riverpod/blob/v1.0.4/packages/riverpod/lib/src/framework/provider_base.dart#L657) +```dart +@override +T read<T>(ProviderBase<T> provider) { + _assertNotOutdated(); + assert(!_debugIsRunningSelector, 'Cannot call ref.read inside a selector'); + assert(_debugAssertCanDependOn(provider), ''); + return _container.read(provider); +} +``` + +A noter que la surveillance vient créer un lien de dépendance entre les `Provider`s et peut mener à l'apparition de dépendances cycliques : + +[_source : dependance_cyclique.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/dependance_cyclique.dart) +```dart +final Matcher throwsProviderException = throwsA(const TypeMatcher<ProviderException>()); + +final Provider<int> provider = Provider<int>((ref) => ref.watch(otherProvider)); // <1> +final Provider<int> otherProvider = Provider<int>((ref) => ref.watch(provider)); + +void main() { + test('doit lever une exception suite à une dépendance cyclique', () { + // given: + final container = createContainer(); + + // expect: + expect(() => container.read(provider), throwsProviderException); // <2> + }); +} +``` + +Une interdépendance est déclarée entre deux `Provider` `<1>` et à la lecture de l'un d'entre eux une exception est levée `<2>`. + +> Riverpod dispose d'un mécanisme qui vient lever une exception quand une dépendance cyclique est détectée. + +## Le listener qui écoutait à l'oreille des Providers + +Un autre moyen d'être notifié d'un changement d'état est de l'écouter avec la méthode `listen` proposée par le `ProviderContainer`. +Cette méthode prend en paramètre une callback qui sera appelée lors de chaque changement d'état en passant en paramètres l'ancienne et la nouvelle valeur. + +[_source : listener.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/listener.dart) +```dart +final intProvider = StateProvider<int>((_) => 13); // <1> +final watchProvider = Provider<int>((ref) => ref.watch(intProvider)); + +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); + + var watchValue = defaultValue; + container.listen<int>(watchProvider, (_, next) => watchValue = next); + + // expect: + expect(intValue, equals(defaultValue)); // <3> + expect(watchValue, equals(defaultValue)); + + // when: + container.read(intProvider.notifier).state = 42; // <4> + + // then: + expect(intValue, equals(42)); // <5> + expect(watchValue, equals(defaultValue)); + + // when: + container.read(watchProvider); // <6> + + // then: + expect(intValue, equals(42)); // <7> + expect(watchValue, equals(42)); + }); +} +``` + + + +Le `StateProvider` `intProvider` est initialisé avec la valeur 13 et `watchProvider` surveille ses modifications `<1>`. +Des listeners écoutent leurs changements d'état respectif pour stocker les nouvelles valeurs `<2>`. +Sans aucune modification, ces valeurs écoutées conservent leurs valeurs par défaut `<3>`. +Après la modification de l'état du `intProvider` avec la valeur 42 `<4>` seulement son listener a été notifié `<5>`. +Ce n'est qu'après la lecture du `watchProvider` que son listener est notifié `<6>`. + +> Écouter n'est pas surveiller. + +Écouter un provider avec la méthode `listen` permet d'être notifié lors d'un changement d'état. +Ce changement d'état ne sera effectif qu'à partir du moment où il sera lue et non à partir du moment où il a été modifié, c'est un __fonctionnement passif__. +C'est pour cette raison que la valeur de `watchValue` reste à -1 `<6>`. + +Surveiller un provider avec la méthode `watch` vient lire le nouvel état à la suite d'un changement. +Ce changement d'état est effectif dès sa modification, c'est un __fonctionnement actif__. + +> Riverpod intialise les états de manière paresseuse (lazy). +> Un état ne sera initialisé qu'à partir du moment où il sera lu, par un `read` ou un `watch`. + +## Devenir sélectif dans les changements d'états + +Être notifié par tous les changements d'états peut mener à des pertes de performances. +Ce problème est résolu par la méthode `select` des `Provider` qui permet d'agréger leur état pour ne conserver que les valeurs utiles. + +[_source : select.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/select.dart) +```dart +final intProvider = StateProvider((_) => 13); // <1> +final moduloProvider = Provider<int>((ref) => ref.watch(intProvider.select((state) => state % 10))); + +void main() { + test('doit écouter le modulo 10', () { + // given: + final container = createContainer(); + + // and: + var called = 0; + container.listen(moduloProvider, (_, __) => called++); // <2> -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + // expect: + expect(container.read(moduloProvider), equals(3)); // <3> + expect(called, equals(0)); -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + // when: + container.read(intProvider.notifier).state = 42; // <4> -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. + // then: + expect(container.read(moduloProvider), equals(2)); // <5> + expect(called, equals(1)); -## Contributing -State if you are open to contributions and what your requirements are for accepting them. + // when: + container.read(intProvider.notifier).state = 22; // <6> + + // expect: + expect(container.read(moduloProvider), equals(2)); // <7> + expect(called, equals(1)); + }); +} +``` + + + +Le `StateProvider` `intProvider` est initialisé avec la valeur 13 et le `moduloProvider` vient l'écouter en sélectionnant uniquement le modulo de 10 de l'état `<1>`. +Un compteur écoute le nombre de changements réalisés par `moduloProvider` `<2>`. +Initialement le modulo de 13 vaut 3 et aucun changement n'est encore réalisé `<3>`. +Suite à la modification de la valeur de `intProvider` par 42 `<4>`, le modulo vaut 2 et un changement est ajouté au compteur `<5>`. +La valeur du `intProvider` est à nouveau modifiée avec la valeur 22 `<6>` mais étant donné que son modulo est identique à celui de 42 aucune modification n'est apportée à l'état du `moduloProvider` `<7>`. + +A noter que cet exemple n'a vocation qu'à présenter la théorie : l'utilisation du `select` dans l'initialisation d'un provider ne présente aucun intérêt étant donné que le comportement est identique à celui d'un `Provider` effectuant lui même l'opération : + +```dart +final moduloProvider = Provider<int>((ref) => ref.watch(intProvider) % 10); +``` + +Le `select` prendra tout son intérêt lors de l'intégration avec Flutter afin de ne conserver que les données utiles à surveiller pour économiser les rebuild et gagner en performances. + +## Accéder au notifier avec le StateNotifierProvider + +Jusqu'à présent les `Provider`s ne donnaient accès qu'à un état, qu'il soit non modifiable avec un `Provider` ou modifiable avec un `StateProvider` par l'intermédiaire de son `notifier`. +Le `StateNotifierProvider` donne accès à ce `notifier` pour permettre au développeur de l'enrichir avec de nouvelles méthodes : + +```dart +final incrementProvider = StateNotifierProvider<IncrementNotifier, int>( + (ref) => IncrementNotifier(ref.watch(intProvider)), +); + +class IncrementNotifier extends StateNotifier<int> { + IncrementNotifier(int value) : super(value); + + void increment() { + state++; + } +} +``` + +Le `StateNotifierProvider` est formé de deux composants : le __contenant__ avec la classe `StateNotifier` et le __contenu__ avec sa variable `state`. +Chacun dispose de son propre cycle de vie, celui de l'état étant dépendant de celui du notifier : + +[_source : state_notifier_provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/state_notifier_provider.dart) +```dart +final intProvider = StateProvider<int>((ref) => 13); // <1> + +final incrementProvider = StateNotifierProvider<IncrementNotifier, int>( + (ref) => IncrementNotifier(ref.watch(intProvider)), +); + +class IncrementNotifier extends StateNotifier<int> { + IncrementNotifier(int value) : super(value); + + void increment() { // <2> + state++; + } +} + +void main() { + test('doit incrémenter un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(incrementProvider), equals(13)); // <3> + + // when: + container.read(incrementProvider.notifier).increment(); // <4> + + // then: + expect(container.read(incrementProvider), equals(14)); // <5> + + // when: + container.read(intProvider.notifier).state = 42; // <6> + + // then: + expect(container.read(incrementProvider), equals(42)); // <7> + }); +} +``` + + + +Le `StateProvider` `intProvider` est initialisé avec la valeur 13 `<1>`. +Il est écouté par le `StateNotifierProvider` `incrementProvider` dont le notifier `IncrementNotifier` dispose d'une méthode pour incrémenté son état `<2>`. +L'état récupéré par le `incrementProvider` est bien celui du `intProvider` `<3>`. +L'appel à la méthode `increment` `<4>` fait passer la valeur du `incrementProvider` de 13 à 14 `<5>`. +Après avoir modifier l'état du `intProvider`, le `incrementProvider` est initialisé à nouveau et prend la valeur 42. + +> Le contrat du `StateNotifierProvider` va de la récupération à la modification de l'état. +> La classe `StateNotifier` est à étendre en indiquant le type du state en générique et de passer sa valeur initiale au constructeur parent. +> L'état est stocké dans la variable `state` avec une visibilité limitée en `protected` pour conserver une implémentation étanche. +> Les modifications sont réalisées en affectant un nouvel objet à la variable `state`. +> Chacune de ces modifications est ensuite notifiée aux objets qui le surveillent. + +## ChangeNotifierProvider, le vilain petit canard + +Jusqu'à présent les états se devaient d'être immutables mais pour des questions de performances ou de conception il est parfois nécessaire d'abandonner cette bonne pratique. +Le `ChangeNotifierProvider` répond à ce cas de figure en laissant à la charge du développeur de notifier les changements apportés à l'état : + +```dart +final incrementProvider = ChangeNotifierProvider<IncrementNotifier>( + (ref) => IncrementNotifier(13), +); + +class IncrementNotifier extends ChangeNotifier { + IncrementNotifier(this.value); + + int value; + + void increment() { + value++; // <1> Modification de l'état + notifyListeners(); // <2> Notification de l'état + } +} +``` + +Comme le `StateNotifierProvider`, il se compose d'un __contenant__ avec le `ChangeNotifier` mais cette fois-ci qui déclare lui-même son propre __contenu__. + +> Le contrat du `ChangeNotifierProvider` va de la récupération à la modification de l'état. +> La classe `ChangeNotifier` est à étendre et les changements internes sont à notifier manuellement en appelant la méthode `notifyListeners`. +> Les notifications sont ensuite transmises aux objets qui les surveillent. + +[_source : change_notifier_provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/change_notifier_provider.dart) +```dart +final incrementProvider = ChangeNotifierProvider<IncrementNotifier>( + (ref) => IncrementNotifier(13), // <1> +); + +class IncrementNotifier extends ChangeNotifier { + IncrementNotifier(this.number); + + int number; + + void increment() { + number++; + notifyListeners(); + } +} + +void main() { + test('doit incrémenter un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(incrementProvider.notifier).number, equals(13)); // <2> + + // when: + container.read(incrementProvider).increment(); // <3> + + // then: + expect(container.read(incrementProvider).number, equals(14)); // <4> + }); +} +``` + + + +La classe `IncrementNotifier`, un `ChangeNotifier` avec une méthode `increment` pour incrémenter sa propriété `number`, est initialisé avec la valeur 13 `<1>`. +La propriété `number` de son `notifier` dispose bien de la valeur 13. +Après avoir été incrémentée `<3>`, la valeur prend la valeur 14 `<4>`. + +Aucune séparation n'étant faite entre le __contenant__ et le __contenu__ du `ChangeNotifier`, il est retourné par le `ProviderContainer` aussi bien en tant qu'état `<2>`, qu'en tant que provider additionnel `notifier` `<4>`. + +A noter que la classe `ChangeNotifier` est initialement proposée par Flutter pour fournir un mécanisme d'écoute et de notification. +Le `ChangeNotifierProvider` est donc parfois utile pour migrer d'anciennes applications utilisant le `ChangeNotifier` comme state management. + +## Détour sur le Future avec le FutureProvider + +La classe `Future` et Riverpod partagent un même monde que tout oppose. +Le `Future` est mutable et utilise des callbacks alors que Riverpod prône l'immutabilité et utilise des états. +Le `FutureProvider` est là pour les réconcilier autour de l'`AsyncValue`. + +L'interface[^1] `AsyncValue` proposée par Riverpod reflète par ses différentes implémentations les états que prend un `Future`. +L'état de chargement, de retour de la donnée et d'erreur sont respectivement représentés par les factories `AsyncValue.loading`, `AsyncValue.data` et `AsyncValue.error`. +À cela vient s'ajouter la méthode statique `AsyncValue.guard` pour transformer un `Future` en `AsyncValue`. + +Pour faciliter son utilisation, `AsyncValue` dispose des mêmes méthodes que [les unions de Freezed](https://pub.dev/packages/freezed#union-types-and-sealed-classes) avec un équivalent au pattern matching avec [l'extension `AsyncValueX`](https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX.html). + +[_source : future_provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/future_provider.dart) +```dart +const duration = Duration(milliseconds: 100); + +final asyncIntProvider = FutureProvider<int>( // <1> + (ref) => Future.delayed(duration, () => 13), +); + +void main() { + test('doit consommer un futur', () async { + // given: + final container = createContainer(); + + // expect: + expect(container.read(asyncIntProvider), equals(const AsyncValue<int>.loading())); // <2> + + // when: + await Future.delayed(duration + const Duration(milliseconds: 50)); // <3> + + // then: + expect(container.read(asyncIntProvider), equals(const AsyncValue.data(13))); // <4> + }); +} +``` + + + +Un `FutureProvider` est instancié pour que son état prenne la valeur 13 après 100ms `<1>`. +Tant que le `Futur` n'a pas été résolu, aucune valeur n'est attribuée à l'état et il conserve la valeur `AsyncLoading` `<2>`. +Après avoir attendu 150ms `<3>`, l'état devient un `AsynData` avec pour valeur 13 `<4>`. + +## Le flux et le StreamProvider + +Le fonctionnement d'une `Stream` est similaire à celui d'un `Future`, à ceci près qu'elle peut retourner plusieurs valeurs durant son cycle de vie : + +[_source : stream_provider.dart_](https://gitlab.ippon.fr/adbonnin/article_flutter_riverpod/-/blob/main/test/stream_provider.dart) +```dart +const duration = Duration(milliseconds: 100); + +final asyncIntProvider = StreamProvider<int>( // <1> + (ref) async* { + await Future.delayed(duration); + yield 13; + + await Future.delayed(duration); + throw Error(); + }, +); + +void main() { + test('doit consommer une stream', () async { + // given: + final container = createContainer(); + + // expect: + expect(container.read(asyncIntProvider), equals(const AsyncValue<int>.loading())); // <2> + + // when: + await Future.delayed(duration + const Duration(milliseconds: 50)); // <3> + + // then: + expect(container.read(asyncIntProvider), equals(const AsyncValue.data(13))); // <4> + + // when: + await Future.delayed(duration); // <5> + + // then: + expect(container.read(asyncIntProvider), isInstanceOf<AsyncError>()); // <6> + }); +} +``` -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +Une `Stream` est déclarée pour retourner la valeur 13 suivie d'une erreur, le tout entrecoupé par un délai de 100 millisecondes `<1>`. +Tant que la première valeur de la `Stream` n'est pas retournée, l'état conserve la valeur `AsyncLoading` `<2>`. +Après avoir attendu 150ms `<3>`, l'état devient un `AsynData` avec pour valeur 13 `<4>`. +Une exception est levée 100ms plus tard `<5>` et l'état retourne une `AsyncError` `<6>`. -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +## Conclusion -## License -For open source projects, say how it is licensed. +Ainsi se termine ce premier article sur Riverpod. +Vous disposez maintenant des bases pour en comprendre les principaux mécanismes. +Le prochain article sera plus court et portera sur son intégration avec Flutter. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +[^1] Les interfaces n'existent pas à proprement parler en Dart, ce sont des classes abstraites avec des méthodes abstraites dont l'interface implicite est implementée. \ No newline at end of file diff --git a/docs/images/change_notifier_provider.png b/docs/images/change_notifier_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..aa8186e1d772a983f830ec17b2f7587938bbc882 GIT binary patch literal 2450 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 QV*mgE07*qoM6N<$g693j#Q*>R literal 0 HcmV?d00001 diff --git a/docs/images/declaration_provider.png b/docs/images/declaration_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..9100e74f7499eab23195885bb14797f3e9789bc1 GIT binary patch literal 2910 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 literal 0 HcmV?d00001 diff --git a/docs/images/future_provider.png b/docs/images/future_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..ee44d69a42723e4287a0bfde0ff9f0543621c0b9 GIT binary patch literal 3192 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 WbJ-r|MDYIv04+6r)mjzn=>Gsc%=Wkd literal 0 HcmV?d00001 diff --git a/docs/images/listener.png b/docs/images/listener.png new file mode 100644 index 0000000000000000000000000000000000000000..0caf619a8af637099cb1717af965d00d8b3c07ea GIT binary patch literal 9733 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 Xhn150kW%2)C(vsdW$6-0qrm?G(~A4< literal 0 HcmV?d00001 diff --git a/docs/images/provider.png b/docs/images/provider.png new file mode 100644 index 0000000000000000000000000000000000000000..ea37a272df4647a9a3f37c9c3edda7dbaf0d4309 GIT binary patch literal 1502 zcmV<41tI#0P)<h;3K|Lk000e1NJLTq00MFV001Hg0ssI2R9&MS00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1#?M6K~#8N?cFg< z+gKdO@hsOn#0^o8UP0ncZUar#Q%lH2w3qaB(Ln);szsHmgQ_~v4N*@pWCMvI3sRR# zj&vt=WX#Bxm9b++h72AvX7tEC+t0>!oLrLHAuivK{=>_&{ruFSKb~Vdi3<P#5E5b) zS3}b@RaGmMN_l^;xRetOp;oK4+ikfA0K^cnijfkj`TqDglb%<mZkDrim0U_Kq|aXF zL_^fp!@J4J36ZiVCnxd%0EiW06(geSy87XRa(n7<DcyMe%f;WT-AbhF?m|kLxh)p2 z@?Zdn31Sta&}y}cxy5qsUi+`#eIeSdw70*C#p~9K=OT~gC;%|_h*d<c(P%umKmX~~ zV_$IgQ?>Q1$YZ{61rQg+Dx#%nT1J@_OHW^3_Fp28i`hpcD}Y!aRuLgx*E92}^X-B! zDF?07jojwyvK$lu#u%}R2yMN1UR%%lGIF@86n?549>{S4U|bQah|1a7S#j<MUpfw5 zdTB+u^<AsglA{B_SRz&tl}viR_k%Qp*2%Ml-9KK-(E(ssnx>V@<%s@FCsq-OaF?o1 zB_}7icL~7fQ&m+_lqkZLSnaJ|N+c5bs{Dk$mG}45LfRLKPBjZj<KMOLw6oJRTX*%l zYj+{V3zz_e!gxnb1mPNftcoDYSDW%fAFBCMT{a!fYF_^TYRZ=e8E_zd%(h-4RtKld z-0t#FO;-G+k}qVd)4o{EmZsB9vt56XNIVF>(dl$Ov$^(6o&x|rTj=3>^{dsOSQSLx zIsMJ5NVceg^?wno18cY2$;k;{B6{m-N333!v23*?)>k5`MPSR*06<7W4A)R&)x2VA ztS;RdXm#dGC2Ild4pDo5hwDIhtQxCVYm*$T+7wymKf+93v-5I!d%5f_)!l7lTi|Rh z&hW-`U9sv~MY~tfJ!f?tou8lIpSk5rL~r$oRckpLoVa>v<(nH{$<qKps6q+X@K>u& z1YR;?Cts}_ot<0zTCKVdGDA!p<UNOXA0y!GPoim$)Oq%R*;)t4J~5CncdK1>UyK%8 z+!GI;RSbxgrS+q1A==s5c``eAtZvMjf0T5+0&LZ{U*9-5IFM%ofUq4M9j&dcC6meJ z<ze1JGpbm163E>)b&pktH?3TWc}bkrJB({DXIsZkU1sa)G53%SIUk2>w7^-#!5GD= zUFN+@UGG{VRslrn3y#&m{w@L5k(lni<dj5L$B==u_4Jr~cp8CX8>><Hyo<oA7jM$} zM6&v(qo0ew0HPGDi@;8v1;nb{y;(PwgZ9P&W7T(B|0J3sQA-{35&~vxoFMQburbxH zS9yt5mtk&M5>aYj`|Yt><(4G?Q5er#mPA6yYoj(Z7mZx9KiO?h0*OG9y(R1WSMN~n zeVwr+Z}0}(vg9yE^z7Ybwif5!BjfsW)i~+p*Q@qnB4TC6_MX)>)%yV(H#=O7ptS`r zRYmcD4S;Zcfe+Z=i`4sd5JMNQg8&eg&;!*%tRfP;yb!=Riy}}h#44h~tM&nmCtkIW zSmKA_09-@FDk8+sm;<<Oh*d;L*Y(VN>U_K4%g90NbR)O9I?Qv~05D?2Dx#%nT1J`e z@~}-`_372)V)hYtDglTEVil2VG#Z(?J7+I*zP#+GKCUblbBkmufVd!5F$y9Vi%Yq8 zKi_NZ{N@YJZgsw0*nDv3-JfrDU6-Q(z}O>JF*+ar{YRPp{%|SX`@tKruH9Wo-B*4* zJw2620zga<s~9P<^n8DOoJr3sQ#Z@mxk@gj7SbX|MMKoq!@J4J39+u_RZ0M2ide<f z&@@d|)k>vO-ro~BYW*Hltybe}Q~<F?tO5WKl8cM~02kZ7a-!XsRR91007*qoM6N<$ Ef=5fx4gdfE literal 0 HcmV?d00001 diff --git a/docs/images/schemas.vsdx b/docs/images/schemas.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..a799ef7d8bc7a7119c6cdfb33219d806720b966f GIT binary patch literal 33089 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 literal 0 HcmV?d00001 diff --git a/docs/images/select.png b/docs/images/select.png new file mode 100644 index 0000000000000000000000000000000000000000..32368d0d69c23fbafac4d8ed94f90dd6cea51ca5 GIT binary patch literal 5505 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 WPhw%n4Kttur1ex!tz6Y6@_zu$g|%Y< literal 0 HcmV?d00001 diff --git a/docs/images/state_notifier_provider.png b/docs/images/state_notifier_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..38e43f4ed761b2d77f69419c85f3cebf8b55de00 GIT binary patch literal 3203 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 literal 0 HcmV?d00001 diff --git a/docs/images/state_provider.png b/docs/images/state_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..343dceef40cc6762ba82eaf7ae57dd3522510a01 GIT binary patch literal 2185 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 LNkvXXu0mjf%}o~Z literal 0 HcmV?d00001 diff --git a/docs/images/stream_provider.png b/docs/images/stream_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf25bae7f3909ce0a859e60f846a9d810b71ef9 GIT binary patch literal 3878 zcmV+>583dEP)<h;3K|Lk000e1NJLTq00MFV001`#0ssI22SzY700001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D4!cQ2K~#8N?VU|* z8^;-j2^2AsIt>*6)JkGIv?W=xV#&0sI1FXSauTNkT-ZWd!%|?>st<(HHi#NHhE8=8 z1Yl7ZAdwbea?+s%bo9XhdDPKI9d+PAM;&?e(S2uUc6WC6hkr{_;=T_)#LL<F<CoF* z&bPZHQ!fc2gb)%FlvP3qAtW{^tAr3jNNiA62_b}#*r2QuLI@$TL0KgSmzw(5qLNSL zE9#gKLiz<|l~_QOB)O6*=?2ayVjPbswu;qLhkf*k-nhC}%Bf*ME+6eFR6Y<}lAgHE z<2eg%*GNlHR*8cMuDPzq*IGU%H9R0v5J)dvU2z;;gB(RV9p?{>FMI1cF<yf`2_XiQ zRicm?i$|;UeY;gGU#pO#Qir?<+9?D&SvOa5+T(<3p96Mms^tA45ur=9&R3jp=C*dj znp21}ggaTLW~p}@G$p&OD)!H!XY|I^C~_PpM-&Hsuj)esdr|>4_W`|eo$OyauvW8* z)F^orSVz`KfrVDNJ&wJe!)lU2gtAH`DhR881o-<`K|w?bS!$Ju1B;TakO)~aP>JB> zl({iQ9P7QxLj`(AJ|MfVln~jhQWv40va!=;&5BKWKia${=9EwYHLS*!yc<;eftsr} z<*v9I?N#??X9Cx&L1%UJn*F9XuB%BY_KBaUfCY*MSf}<mUm@lZyq*J_lL3UXN(5GP z5eIri5-}l<5K&Qa$3nyjGj@r<E007+*6GklZC7ycX0LWvBF3rMX6^9>o-W<UXkm71 zP6?wpr)#Bt$||NKC*EaOi__}RsT<@~AUf5?6@=96FPKx0TqjCJB~>6n?Nm-5Mc^w8 zUeDHHKyxyPP*#cjgLhE@$pxpiG8vLk=0p$e*g3QVmx*3iR`G;%bsFgc@(Nk(geh;B z^A4a-N8p<h{^QgxmAv7ymtZ!^%>ti~a9S(t#MOE$yQ3hl3Vc0;*6b&EN6Kvi-+RVs zt*kTG1t;=>wS9#_>)9fBFfxEpR*CoT-Od70=v*E11S}zkc{^>e(AqnPT4=H^uK6lB z5l3_`2ND(KSy4-rXhp!8hE*DwtZJ|2q5Bp**X4SRXP#YkwR;*zvI|ipO1xeLcI<b` zLu>XCF(+ptU3Q(@481y?dS+nbD-_c(*Rw@nb25lfR+A0_l(YLs43RGoR<zW>iChZL z^;f$+N>p=o4pGE9fncSgj3?UwD$0oHBVW7YI7RXw)ur8o=u!-e4(#D=M9sh{xowrm z^d5U&J$AL*XRuej?Tr$nX20l}>pY&buvV$vUZVCKfrWwVIiw~TP$;XUMG$!U9%Vb> z0L}lPySk6ib)<h#R!Oty8~FnlvYg2yE%78Z`;r6g>i$C4k-kG&C4>+{VuP|u2qA>T z24$5HLI{Zs$|@m*5E2`dRYC|MBsM6kqz}A!@nTEk`Sa)M1Zjoc-CcNZb93{tMYUSR z%X+=8PLrm=3Y4BK6bfQRZf))D?WuEwv<PLD^p5TA?e$0BPNYx2dgzt;(Nm6P)9<8G zsm%Bog4)i`j;c*UfSSUMa98@+5qNKD>g?m$5!k}`yLdTt__c*=SBk|V^|c|;(Us-9 z!$;o43Y4B)pB<HJa(L(sco=@>f+Vd%StY#yf{Iw5IW=6!j_oYZz5Mbszir?75J7F? zt>a6Jx1b!VCDA|%wYqX2Y6>^PU4{c4tKOVhn;A`?I9B-nyB9BBsK!JCnLSh!I=XTF z!ruLBh6AO+!|*c}B&3(gD`_6eD(O1we_Ml~BGwyWE6g@;Oi#RhqWmMj<IO|$*|YTV zqvh*UhP%S7{@~;F?D@>t2=^#D*k>$$_6aikh<%A+kY0Fx<L94L7lcGYStVVhUaupB z6|THz#Hoh%BLC^?`x9%cdDT(k!6sv2;tbqkII5xTEYD@qCwcc;f}}G$HTm@Btl_1G z2G6g|Oss$XmFkF)a44&!TR>CiXQtZ-VU88&r&sR$UUipX9zS}BO@`r|<_&4<{JAue zUDaJuLDrs~h`M;*&<fYMAW35=tE5{jT%D`l{=f)Kts0ay;W_KZ&p#ngGThRt31vM$ zvc0{nI!zvOlh=u+N_+g#G<G?vQ$m8Etdgz(ErcQ(A*nrEyL`U*uYan36UNro*23i3 zHm?2pZTH?)<Z8)$^hVrI{bauLO`f!+>Cw&q{a1BOh!4sti64l;{OBno7`1H>mCWh4 z6Zkm?h(Ts-B<#WIHf-a@#g)5vRPRZm_~gmj>^Z|5Z5tM3`uI^k8%)BWtdjW2jCXVg z%WqE?GK;^xp?XgY@rr@8%&Fm|T?)qTBt87=uusvnXT^`+Tl@2uY5+12)a&)QK8;3M zB{B2#>C?r@Ge#JSu*zgAHQD&HH%ZfjZioRafg6-ocPGs(NG7a<2Le|*oh}xOor^1F zmBh@_;;qda&EHj>$){5Jn?_-P6><~n55G}kB!GA?q&o&Z7&`pRxOU|Sg6zeq=btx! zSGCy%xq4}&TrR6I$UuO7(%RZu=i*9P-B(obC!NY2;IBczS`<;c`A2MSo=gpo*Nnm- z+gYBQNS{{2B!IQQ{-roKZaBfQ+T4&>DRWo+g%&3(TDf71lV`9oSHlcC2$aJ^Zx|kk zvZfbg_2$gNwJT~EGC*`Lt_M|CE&b8RSJa6}O1Z##wNg%XW0A|3YU)fEz|3<|{a5k_ zSytP)0&HkQgdsjA=dv625FrmMiF7JAH&YrmTsM=q^&t8(Ce#|5t%ezNHa9nyrp_83 zh_L1tB=(N49(qL$Lk5mc#PwijtI)$pi82Y1RRFPbPSr|Kwk`^5GzVK&%jNRwr4b_l zg&P!NsPu(V7;4${JIVRBprO|f*B^XrctEo&K3R25q^;p8*XE7srNvunm_bMUvd?U{ z7G(O^5q>e`VAGzsb|9-2Xj));CuGS!k%R83Vmrf>@}*wjtUSu)RU=dRYS0<$gskHc zyZ~7Z>rIX4;%$i2?zm33s<V<a2fdO0HdV2k_U0@4v31g&gE?pSEn>P}S#^7HM*)!& z>nE*6v8FG$NlVVvOZ)+gOvvW}%_3w~rn8|b*VgUX`OC9vq(Nu(!Jo<>oi{uXYAr6v zMD6pEOsnGBfvnbmIxKl|J!n!z$qb^tE)sa@5iecyR3odoD-l))PS>dC(GR1tBD}X| zvsJy3rC@019mZQnyifa;_8tSL=PR@tr~>g0F?abw)YUgEIH^xRcw)^`uh)l$oLh0R z@%k&j*tmYdh&~|}m2U;wA4X27VG_WxR}UF)-)lkMgDOZOA7tIVcNPDeRLw!Bs|zx7 z>{n_Sk{AvhI)vb<!e|KX%c_tPHITp!_}Yn_FY3FRqA8J32&cV%?5Pw$pl*ft7A>m| z-bn{e0Rww`(rP*9e3dNBIUD_3r`=;UB?qm;2EOSJ>yN%IWXFu?6Kr3M=UTbo23wjs zyScfkMjB{3%U0#HOQBCKF<T9`))yqR)#M<a%(rOf|9HEyTC3=i=@l=Bnn=F%Y!yHZ zL{+ckgbHB;=`bZq653mn*{YYoDR`$3^jd0G>YbXHb2j?7P6r(OY2cgwP%IYLW=4(Z z6KEpzH}P>*-R4J6VR5UG2Axjstwt*Z@2v(}8w-;6R+E4usLcnTwq?~7hgwA>RgQw- zgnmuaCk$wrFBuO7g$p5Gc`gEr&ebbmN|YqLH>bKWXS1G8uzGn%Hs?`;q8hh>9ei`f z0J_8Ui@+Xdn(RwiZTi7Wp$g4>I#9PnMX~TFfu;Fm6;f)eLUgv8s3(CpHa1o+HY2O; zEXY)<gPUk%-_wSmO4?r*i0bmXq@|MLK<W}y(|%qk<kp{%4zHp~P-Y){BunefzG>?= zQaJrZ_oh$Uof(u)mI7U>LI<%3cx1^z^ysba9Ytc-a4(y~UzRUjW!2w4JS!(1J+XeW z*W||pMLHGr*-F3d-M=<`<V`h90to-ys(2zv#}-#13^q|0GZXc>RqVsC8#6o*V6896 zL|scy8f^=zq#wJjeVJyFxInBy#4%!!VQpM++qiyVW%;feBLRp%Y<|m995l-Ng|)FD z*DsG3e)vI+K?a2m1y#~}Sw+&ef08Q_KTUjewVexY3*+xrt5r2d0)X=IJAq?i{oyx- zT=WmtwzD87-a4MZUsW1lc6WC>7gR|PWz};Ll*&4=bVCoe?|hgUA5#M)2L6IgEbQ&= zrH>zN;R`jJuw?$E(qMzXi_QgA!dvbne#-y(_v&xr)Ptm@suFBa_<O6dz~5UXy@aw# zx<WH|SevxOiUGj(d13NwESc8sy{p-Yv1Ez?HYnMt$+&(4cs+Na@Q=T%t_kr$StVVe zUax0H&mfc=A*oe^HZNTHFj0@#7;OCf)5=Wrt-;1tfAI1A`H`eu{?);L0=tUN7i9U< zORImpr#dAh2+Aty7CSpT+0k?-f$aI`AI#58H~gxwf#>m~hpX?mudG5-&!wx+o~iDV z3U(FPTXeD@Hy36WKbcSRCXX}&WtDUh1Ty5Tt^A0zYY1WUvzMr;lJbwGg^4q5Tn*k? zp39_9@;mk<SdhqB+q)WExHi3X>vz;t(h$lj=`PU4?8T|I*>lZ58;pEnY3eLPm;utw z^zozR>r;li!VF33diH!~Y=mF1r?a=Wml+>xX+dt?p3SD;S^xSg)deAuP*zDVC_Z^I zaq8{0nbGLXwR&@A_0q`1>5~chO8r)WB&@F7Paiu{$VR1Ha3fruK5?w@{de4&1mY~n zhzIMiFWI<$VR7=z{N-7u`=og&tE5LDZ7mjy$Xthq-dGra_wnqAW9ze{^P{JR4!^c= z?Mk^^rV>Ph>~sCmw@A65ukaf(d+A1a)o~+S&FkmU$OQ?>gva1F?B|>X2~Q$D9eVxn z%JSXhe9<F`3(6|#HAuOt)vEYu5t~Axu(h>C8E6&gEBuDcUb+!pr5oD-9)sVopObC` o@Fdey(l(S;LI@!wCSJb$KlFaHcfBSBkpKVy07*qoM6N<$g8A8LO8@`> literal 0 HcmV?d00001 diff --git a/docs/images/test_overrides.png b/docs/images/test_overrides.png new file mode 100644 index 0000000000000000000000000000000000000000..a17557e24cbc9ee393f52389df4deebced02130c GIT binary patch literal 3232 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 M3``LvdUu}x6Q{aBl>h($ literal 0 HcmV?d00001 diff --git a/docs/images/watch.png b/docs/images/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..7789c1f6910372873ed9b7626b54799ed1d9fc59 GIT binary patch literal 6505 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 literal 0 HcmV?d00001 diff --git a/test/change_notifier_provider.dart b/test/change_notifier_provider.dart new file mode 100644 index 0000000..cc2fa95 --- /dev/null +++ b/test/change_notifier_provider.dart @@ -0,0 +1,36 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final incrementProvider = ChangeNotifierProvider<IncrementNotifier>( + (ref) => IncrementNotifier(13), // <1> +); + +class IncrementNotifier extends ChangeNotifier { + IncrementNotifier(this.number); + + int number; + + void increment() { + number++; + notifyListeners(); + } +} + +void main() { + test('doit incrémenter un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(incrementProvider.notifier).number, equals(13)); // <2> + + // when: + container.read(incrementProvider).increment(); // <3> + + // then: + expect(container.read(incrementProvider).number, equals(14)); // <4> + }); +} diff --git a/test/dependance_cyclique.dart b/test/dependance_cyclique.dart new file mode 100644 index 0000000..abe30b7 --- /dev/null +++ b/test/dependance_cyclique.dart @@ -0,0 +1,19 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final Matcher throwsProviderException = throwsA(const TypeMatcher<ProviderException>()); + +final Provider<int> provider = Provider<int>((ref) => ref.watch(otherProvider)); // <1> +final Provider<int> otherProvider = Provider<int>((ref) => ref.watch(provider)); + +void main() { + test('doit lever une exception suite à une dépendance cyclique', () { + // given: + final container = createContainer(); + + // expect: + expect(() => container.read(provider), throwsProviderException); // <2> + }); +} diff --git a/test/future_provider.dart b/test/future_provider.dart new file mode 100644 index 0000000..5e81976 --- /dev/null +++ b/test/future_provider.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +const duration = Duration(milliseconds: 100); + +final asyncIntProvider = FutureProvider<int>( // <1> + (ref) => Future.delayed(duration, () => 13), +); + +void main() { + test('doit consommer un futur', () async { + // given: + final container = createContainer(); + + // expect: + expect(container.read(asyncIntProvider), equals(const AsyncValue<int>.loading())); // <2> + + // when: + await Future.delayed(duration + const Duration(milliseconds: 50)); // <3> + + // then: + expect(container.read(asyncIntProvider), equals(const AsyncValue.data(13))); // <4> + }); +} diff --git a/test/listener.dart b/test/listener.dart new file mode 100644 index 0000000..934fd4f --- /dev/null +++ b/test/listener.dart @@ -0,0 +1,40 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final intProvider = StateProvider<int>((_) => 13); // <1> +final watchProvider = Provider<int>((ref) => ref.watch(intProvider)); + +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); + + var watchValue = defaultValue; + container.listen<int>(watchProvider, (_, next) => watchValue = next); + + // expect: + expect(intValue, equals(defaultValue)); // <3> + expect(watchValue, equals(defaultValue)); + + // when: + container.read(intProvider.notifier).state = 42; // <4> + + // then: + expect(intValue, equals(42)); // <5> + expect(watchValue, equals(defaultValue)); + + // when: + container.read(watchProvider); // <6> + + // then: + expect(intValue, equals(42)); // <7> + expect(watchValue, equals(42)); + }); +} diff --git a/test/provider.dart b/test/provider.dart new file mode 100644 index 0000000..66bc6c6 --- /dev/null +++ b/test/provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final intProvider = Provider<int>((_) => 13); // <1> + +void main() { + test('doit récupérer un état', () { + // given: + final container = ProviderContainer(); // <2> + addTearDown(container.dispose); + + // expect: + expect(container.read(intProvider), equals(13)); // <3> + }); +} diff --git a/test/select.dart b/test/select.dart new file mode 100644 index 0000000..2fe9dae --- /dev/null +++ b/test/select.dart @@ -0,0 +1,36 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final intProvider = StateProvider((_) => 13); // <1> +final moduloProvider = Provider<int>((ref) => ref.watch(intProvider.select((state) => state % 10))); + +void main() { + test('doit écouter le modulo 10', () { + // given: + final container = createContainer(); + + // and: + var called = 0; + container.listen(moduloProvider, (_, __) => called++); // <2> + + // expect: + expect(container.read(moduloProvider), equals(3)); // <3> + expect(called, equals(0)); + + // when: + container.read(intProvider.notifier).state = 42; // <4> + + // then: + expect(container.read(moduloProvider), equals(2)); // <5> + expect(called, equals(1)); + + // when: + container.read(intProvider.notifier).state = 22; // <6> + + // expect: + expect(container.read(moduloProvider), equals(2)); // <7> + expect(called, equals(1)); + }); +} diff --git a/test/state_notifier_provider.dart b/test/state_notifier_provider.dart new file mode 100644 index 0000000..b9445bd --- /dev/null +++ b/test/state_notifier_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'utils.dart'; + +final intProvider = StateProvider<int>((ref) => 13); // <1> + +final incrementProvider = StateNotifierProvider<IncrementNotifier, int>( + (ref) => IncrementNotifier(ref.watch(intProvider)), +); + +class IncrementNotifier extends StateNotifier<int> { + IncrementNotifier(int value) : super(value); + + void increment() { // <2> + state++; + } +} + +void main() { + test('doit incrémenter un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(incrementProvider), equals(13)); // <3> + + // when: + container.read(incrementProvider.notifier).increment(); // <4> + + // then: + expect(container.read(incrementProvider), equals(14)); // <5> + + // when: + container.read(intProvider.notifier).state = 42; // <6> + + // then: + expect(container.read(incrementProvider), equals(42)); // <7> + }); +} diff --git a/test/state_provider.dart b/test/state_provider.dart new file mode 100644 index 0000000..edd0f88 --- /dev/null +++ b/test/state_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final intProvider = StateProvider((_) => 13); // <1> + +void main() { + test('doit modifier un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(intProvider), equals(13)); + + // when: + container.read(intProvider.notifier).state = 42; // <2> + + // expect: + expect(container.read(intProvider), equals(42)); // <3> + }); +} diff --git a/test/stream_provider.dart b/test/stream_provider.dart new file mode 100644 index 0000000..ad37191 --- /dev/null +++ b/test/stream_provider.dart @@ -0,0 +1,38 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +const duration = Duration(milliseconds: 100); + +final asyncIntProvider = StreamProvider<int>( // <1> + (ref) async* { + await Future.delayed(duration); + yield 13; + + await Future.delayed(duration); + throw Error(); + }, +); + +void main() { + test('doit consommer une stream', () async { + // given: + final container = createContainer(); + + // expect: + expect(container.read(asyncIntProvider), equals(const AsyncValue<int>.loading())); // <2> + + // when: + await Future.delayed(duration + const Duration(milliseconds: 50)); // <3> + + // then: + expect(container.read(asyncIntProvider), equals(const AsyncValue.data(13))); // <4> + + // when: + await Future.delayed(duration); // <5> + + // then: + expect(container.read(asyncIntProvider), isInstanceOf<AsyncError>()); // <6> + }); +} diff --git a/test/test_overrides.dart b/test/test_overrides.dart new file mode 100644 index 0000000..0d1d66e --- /dev/null +++ b/test/test_overrides.dart @@ -0,0 +1,23 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final intProvider = Provider<int>((_) => 13); // <1> +final otherIntProvider = Provider<int>((_) => 13); + +void main() { + test('doit surcharger le comportement des Provider', () { + // given: + final container = createContainer( + overrides: [ + intProvider.overrideWithProvider(Provider<int>((_) => 42)), // <2> + otherIntProvider.overrideWithValue(42), + ], + ); + + // expect: + expect(container.read(intProvider), 42); // <3> + expect(container.read(otherIntProvider), 42); + }); +} diff --git a/test/utils.dart b/test/utils.dart new file mode 100644 index 0000000..0d35a8d --- /dev/null +++ b/test/utils.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +ProviderContainer createContainer({ + ProviderContainer? parent, + List<Override> overrides = const [], + List<ProviderObserver>? observers, +}) { + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + addTearDown(container.dispose); + return container; +} diff --git a/test/watch.dart b/test/watch.dart new file mode 100644 index 0000000..876dfe8 --- /dev/null +++ b/test/watch.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +final intProvider = StateProvider((_) => 13); // <1> + +void main() { + test('doit surveiller un état', () { + // given: + final container = createContainer(); + + // expect: + expect(container.read(intProvider), equals(13)); + + // when: + container.read(intProvider.notifier).state = 42; // <2> + + // expect: + expect(container.read(intProvider), equals(42)); // <3> + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 35e5611..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:article_flutter_riverpod/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} -- GitLab