From 51e7e0becd923a242192e2257a8f556f777eaa32 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:05:20 +0200 Subject: [PATCH 01/38] A gentle introduction --- website/docs/riverpod_for_provider_users.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 475a36a21..6601a611c 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -18,6 +18,18 @@ import { This article is designed for people familiar with the [Provider] package who wants to learn about Riverpod. +But why was Riverpod created in the first place? + +## Provider's limitations +Provider has fundamental issues due to being restricted by the InheritedWidget API. +Indeed, it is good to keep in mind that, inherently, Provider is a "simpler InheritedWidget". +Provider is merely an InheritedWidget wrapper, and thus it's limited by it. + +By the end of this read you should be convinced that Riverpod should be used instead of Provider. +**Riverpod is the more modern, recommended and reliable approach**, whereas Provider is currently lacking in many areas with no way forward. + +Such limitations include the following. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From 18a895ec38b9bee9bc9d1ee0919165fcfe7f3fc5 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:05:49 +0200 Subject: [PATCH 02/38] Provider cant keep same type providers --- website/docs/riverpod_for_provider_users.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 6601a611c..81f9b2376 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -30,6 +30,11 @@ By the end of this read you should be convinced that Riverpod should be used ins Such limitations include the following. +### Provider can't keep two (or more) providers of the same "type" +Declaring two `Provider` will result into unreliable behaviors: InheritedWidget's API will +obtain only one: the closest `int` ancestor. While a [workaround] is explained in Provider's +documentation, Riverpod simply doesn't have this problem. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From f0850e225f58346f3fe039045e0bf427b2de7fb1 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:06:34 +0200 Subject: [PATCH 03/38] Provider can't emit multiple values --- website/docs/riverpod_for_provider_users.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 81f9b2376..2f3769bc6 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -35,6 +35,13 @@ Declaring two `Provider` will result into unreliable behaviors: InheritedWi obtain only one: the closest `int` ancestor. While a [workaround] is explained in Provider's documentation, Riverpod simply doesn't have this problem. +### Providers reasonably emit only one value at a time +When reading an external RESTful API, it's quite common to want to show +the last read value, while a new requested call loads the next one. +Riverpod allows this behavior via emitting two values at a time (a previous data value, +and an incoming new loading value), via `AsyncValue`'s APIs. +With Provider, this isn't as easy, or even achieavable. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From 4d4045f281f3eabfce43ac8dc437fa2934d12908 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:07:01 +0200 Subject: [PATCH 04/38] Provider cant combine state --- website/docs/riverpod_for_provider_users.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 2f3769bc6..0afc2b47b 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -42,6 +42,12 @@ Riverpod allows this behavior via emitting two values at a time (a previous data and an incoming new loading value), via `AsyncValue`'s APIs. With Provider, this isn't as easy, or even achieavable. +### Combining providers is *very* difficult. +With Provider we may be tempted to enable the usage of `context.watch` inside provider's create. +This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency +has changed (e.g. such as when there's a GlobalKey involved in the widget tree). +Combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From e3d6b1070d83ba94f6f814ec4e40dd95b4a62f13 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:07:31 +0200 Subject: [PATCH 05/38] Provider isn't safe --- website/docs/riverpod_for_provider_users.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 0afc2b47b..033af5989 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -48,6 +48,11 @@ This would be unreliable, as `didChangeDependencies` may be triggered even if no has changed (e.g. such as when there's a GlobalKey involved in the widget tree). Combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead. +### Lack of safety. +It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. +Indeed, this runtime exception was one of the main reasons Riverpod was created in the first place. +With Riverpod, this exceptin simply doesn't exist. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From 9bae4a7f0eab47b91a4abd7b7f609a78c1fc1738 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:08:46 +0200 Subject: [PATCH 06/38] Disposing of state is difficult in Provider --- website/docs/riverpod_for_provider_users.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 033af5989..835bd151d 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -53,6 +53,15 @@ It's common to end-up with a `ProviderNotFoundException` during refactors and / Indeed, this runtime exception was one of the main reasons Riverpod was created in the first place. With Riverpod, this exceptin simply doesn't exist. +### Disposing of state is difficult +`InheritedWidget` *can't* react when a consumer stops liste to them. This prevents the ability for Provider +to automatically destroy its providers' state when they're no-longer used. +With Provider, [we have to] rely on scoping providers to dispose the state when it stops being used. +But this isn't easy, as it gets tricky when state is shared between pages. +Riverpod solves with easy-to-understand APIs such as [the .autoDispose modifier] and [its keepAlive method]. +Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). +Unluckily, there's no way to implement this with raw `InheritedWidgets`, thus with Provider. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From cf54d8e14fe2ac3f88a90d6fcc123ad48d1ffdee Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:09:14 +0200 Subject: [PATCH 07/38] Provider has no .family --- website/docs/riverpod_for_provider_users.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 835bd151d..b34b633c5 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -62,6 +62,23 @@ Riverpod solves with easy-to-understand APIs such as [the .autoDispose modifier] Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). Unluckily, there's no way to implement this with raw `InheritedWidgets`, thus with Provider. +### Lack of a reliable parametrization mechanism +Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. +Indeed, `.family` is one of the most powerful feature of Riverpod and it's core to its innovations, +e.g. it enables enormous [simplification of logic]. + +If we wanted to implement something similar using Provider, we would have to give +up easiness of use *and* type-safeness on such parameters. + +Furthermore, not being able to implement a similar `.autoDispose` mechanism with Provider +inherently prevents any equivalent implementation of `.family`, too: indeed, +these two features [go hand-in-hand]. + +Finally, [it turns out] that widgets *never* stop to listen to an `InheritedWidget`; this +implies significant memory leaks if some provider state is "dynamically mounted", +i.e. when passing parameters to providers, thus obtaining a `.family` equivalent for Provider +is fundamentally impossible at the moment in time. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From 3917f588df6e0f00eb7cbfb7facf6d58fd7d0d73 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:09:44 +0200 Subject: [PATCH 08/38] Triggering side effects is not cool --- website/docs/riverpod_for_provider_users.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index b34b633c5..f59b0a3e2 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -79,6 +79,11 @@ implies significant memory leaks if some provider state is "dynamically mounted" i.e. when passing parameters to providers, thus obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. +### Triggering side effects isn't straightforward +Recall that Provider is a wrapper around `InheritedWidget`'s APIs: there's no `onChange` callback in there. +This is problematic for navigation, such as for snackbars, modals, etc. +Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. + ## The relationship between Riverpod and Provider Riverpod is designed to be the spiritual successor of Provider. Hence the From 5965d81f412787cd1dd6b03e5550c3efd9a6ac2e Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:10:49 +0200 Subject: [PATCH 09/38] Rephrasing and restructuring of the introduction --- website/docs/riverpod_for_provider_users.mdx | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index f59b0a3e2..182a4a189 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -84,18 +84,22 @@ Recall that Provider is a wrapper around `InheritedWidget`'s APIs: there's no `o This is problematic for navigation, such as for snackbars, modals, etc. Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. -## The relationship between Riverpod and Provider - -Riverpod is designed to be the spiritual successor of Provider. Hence the -name "Riverpod", which is an anagram of "Provider". - -Riverpod was born while searching for solutions to the various technical -limitations that Provider face. Originally, Riverpod was supposed to be a -major version of Provider as a way to solve this problem. But it was -decided against as this would be a decently big breaking change, and -Provider is one of the most used Flutter packages. - -Still, conceptually, Riverpod and Provider are fairly similar. +## Riverpod's birth +Originally, a major version of Provider was planned to ship, as a way to solve +the aforementioned problems. But it was decided against as this would have been "too breaking" +and even controversial, because of the new `ConsumerWidget` API. +Since Provider is still one of the most used Flutter packages, tt was instead decided +to make a separate package, and this Riverpod was created. + +Creating a separate package enabled: + 1. Ease of migration for whoever wants to, by also enabling the use of both approaches *at the same time* temporarily; + 2. Allow folks to stick to Provider if they dislike Riverpod in principle, or if they weren't just confident enough yet; + 3. Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. + +Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod", which is an anagram of "Provider". + +### The relationship between Riverpod and Provider +Still, conceptually, Riverpod and Provider are fairly similar. Both packages fill a similar role. Both try to: - cache and dispose some stateful objects From 483fd15db8a748c914c9825b57715d958e47129e Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:11:25 +0200 Subject: [PATCH 10/38] Adding links up until now --- website/docs/riverpod_for_provider_users.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 182a4a189..3d6ff8a83 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -480,3 +480,12 @@ last value before the provider changed and the new value after it changed. [ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider [ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change [autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[.autoDispose]: /docs/concepts/modifiers/auto_dispose +[its keepAlive method]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[simplification of logic]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106546 +[integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change From 12378b69d090d418fa62699d1dd757f274888c7f Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:13:34 +0200 Subject: [PATCH 11/38] Changed the catching phrase --- website/docs/riverpod_for_provider_users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 3d6ff8a83..80841fe47 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -18,7 +18,7 @@ import { This article is designed for people familiar with the [Provider] package who wants to learn about Riverpod. -But why was Riverpod created in the first place? +Since Provider is widely popular, why would one migrate to Riverpod instead? ## Provider's limitations Provider has fundamental issues due to being restricted by the InheritedWidget API. From af67e8e75992638aeea09c63b038c4db7d24bc90 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:36:12 +0200 Subject: [PATCH 12/38] One full read, one full correction --- website/docs/riverpod_for_provider_users.mdx | 78 ++++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 80841fe47..e180ab053 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -18,92 +18,92 @@ import { This article is designed for people familiar with the [Provider] package who wants to learn about Riverpod. -Since Provider is widely popular, why would one migrate to Riverpod instead? +Since Provider is widely popular, why would one migrate to Riverpod? ## Provider's limitations Provider has fundamental issues due to being restricted by the InheritedWidget API. -Indeed, it is good to keep in mind that, inherently, Provider is a "simpler InheritedWidget". +Inherently, Provider is a "simpler `InheritedWidget`", or, in other words, Provider is merely an InheritedWidget wrapper, and thus it's limited by it. -By the end of this read you should be convinced that Riverpod should be used instead of Provider. -**Riverpod is the more modern, recommended and reliable approach**, whereas Provider is currently lacking in many areas with no way forward. +By the end of this paragraph you should be convinced that Riverpod is to be prefered over Provider. +**Riverpod is indeed a more modern, recommended and reliable approach towards State Management, Caching and Reactivty**, +whereas Provider is currently lacking in many areas with no way forward. -Such limitations include the following. +Here's a list of known Provider issues. ### Provider can't keep two (or more) providers of the same "type" -Declaring two `Provider` will result into unreliable behaviors: InheritedWidget's API will -obtain only one: the closest `int` ancestor. While a [workaround] is explained in Provider's +Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will +obtain only one of the two: the closest `Provider` ancestor. While a [workaround] is explained in Provider's documentation, Riverpod simply doesn't have this problem. ### Providers reasonably emit only one value at a time -When reading an external RESTful API, it's quite common to want to show +When reading an external RESTful API, it's quite common to show the last read value, while a new requested call loads the next one. -Riverpod allows this behavior via emitting two values at a time (a previous data value, +Riverpod allows this behavior via emitting two values at a time (i.e. a previous data value, and an incoming new loading value), via `AsyncValue`'s APIs. -With Provider, this isn't as easy, or even achieavable. +With Provider, this isn't as easy, or achievable, even. ### Combining providers is *very* difficult. -With Provider we may be tempted to enable the usage of `context.watch` inside provider's create. +With Provider we may be tempted to use `context.watch` inside provider's `create`. This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency has changed (e.g. such as when there's a GlobalKey involved in the widget tree). -Combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead. +Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead. ### Lack of safety. It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. -Indeed, this runtime exception was one of the main reasons Riverpod was created in the first place. -With Riverpod, this exceptin simply doesn't exist. +Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. +Although Riverpod brings much more utility than this, this exception simply can't be thrown. ### Disposing of state is difficult -`InheritedWidget` *can't* react when a consumer stops liste to them. This prevents the ability for Provider +`InheritedWidget` [*can't* react when a consumer stops listening to them]. This prevents the ability for Provider to automatically destroy its providers' state when they're no-longer used. With Provider, [we have to] rely on scoping providers to dispose the state when it stops being used. But this isn't easy, as it gets tricky when state is shared between pages. -Riverpod solves with easy-to-understand APIs such as [the .autoDispose modifier] and [its keepAlive method]. +Riverpod solves this with easy-to-understand APIs such as [autodispose] and [keepAlive]. Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). -Unluckily, there's no way to implement this with raw `InheritedWidgets`, thus with Provider. +Unluckily, there's no way to implement this with raw `InheritedWidgets`, and thus with Provider. ### Lack of a reliable parametrization mechanism Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. -Indeed, `.family` is one of the most powerful feature of Riverpod and it's core to its innovations, +Indeed, `.family` is one of Riverpod's most powerful feature and it is core to its innovations, e.g. it enables enormous [simplification of logic]. If we wanted to implement something similar using Provider, we would have to give up easiness of use *and* type-safeness on such parameters. Furthermore, not being able to implement a similar `.autoDispose` mechanism with Provider -inherently prevents any equivalent implementation of `.family`, too: indeed, -these two features [go hand-in-hand]. +inherently prevents any equivalent implementation of `.family`, [as these two features go hand-in-hand]. -Finally, [it turns out] that widgets *never* stop to listen to an `InheritedWidget`; this -implies significant memory leaks if some provider state is "dynamically mounted", -i.e. when passing parameters to providers, thus obtaining a `.family` equivalent for Provider -is fundamentally impossible at the moment in time. +Finally, as shown before, [it turns out] that widgets *never* stop to listen to an `InheritedWidget`; this +implies significant memory leaks if some provider state is "dynamically mounted", i.e. when using parameters +to a build a Provider, which is exactly what `.family` does. +Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. ### Triggering side effects isn't straightforward -Recall that Provider is a wrapper around `InheritedWidget`'s APIs: there's no `onChange` callback in there. +Since `InheritedWidget` has no `onChange` callback, Provider can't have one. This is problematic for navigation, such as for snackbars, modals, etc. Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. ## Riverpod's birth Originally, a major version of Provider was planned to ship, as a way to solve -the aforementioned problems. But it was decided against as this would have been "too breaking" -and even controversial, because of the new `ConsumerWidget` API. -Since Provider is still one of the most used Flutter packages, tt was instead decided -to make a separate package, and this Riverpod was created. +the aforementioned problems. But it was then decided against it, as this would have been +"too breaking" and even controversial, because of the new `ConsumerWidget` API. +Since Provider is still one of the most used Flutter packages, it was instead decided +to create a separate package, and thus Riverpod was created. Creating a separate package enabled: - 1. Ease of migration for whoever wants to, by also enabling the use of both approaches *at the same time* temporarily; - 2. Allow folks to stick to Provider if they dislike Riverpod in principle, or if they weren't just confident enough yet; - 3. Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. + - Ease of migration for whoever wants to, by also enabling the temporary use of both approaches, *at the same time*; + - Allow folks to stick to Provider if they dislike Riverpod in principle, or if they didn't find it reliable yet; + - Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. -Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod", which is an anagram of "Provider". +Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). ### The relationship between Riverpod and Provider Still, conceptually, Riverpod and Provider are fairly similar. Both packages fill a similar role. Both try to: -- cache and dispose some stateful objects -- offer a way to mock those objects during tests +- cache and dispose some stateful objects; +- offer a way to mock those objects during tests; - offer a way for Widgets to listen to those objects in a simple way. At the same time, think of Riverpod as what Provider could've been if @@ -482,10 +482,10 @@ last value before the provider changed and the new value after it changed. [autodispose]: /docs/concepts/modifiers/auto_dispose [workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type [.family modifier]: /docs/concepts/modifiers/family -[.autoDispose]: /docs/concepts/modifiers/auto_dispose -[its keepAlive method]: /docs/concepts/modifiers/auto_dispose#refkeepalive -[go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[as these two features go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant [simplification of logic]: /docs/concepts/modifiers/family#usage [we have to]: https://github.com/flutter/flutter/issues/128432 -[it turns out]: https://github.com/flutter/flutter/issues/106546 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 [integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change From 6acedde188ed9cdbb903f3d6035bf68de91c52b1 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:39:38 +0200 Subject: [PATCH 13/38] Moved the ProxyProvider section where it belongs --- website/docs/riverpod_for_provider_users.mdx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index e180ab053..06537e720 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -43,11 +43,14 @@ Riverpod allows this behavior via emitting two values at a time (i.e. a previous and an incoming new loading value), via `AsyncValue`'s APIs. With Provider, this isn't as easy, or achievable, even. -### Combining providers is *very* difficult. +### Combining providers is hard and error prone. With Provider we may be tempted to use `context.watch` inside provider's `create`. This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency -has changed (e.g. such as when there's a GlobalKey involved in the widget tree). -Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead. +has changed (e.g. such as when there's a GlobalKey involved in the widget tree). + +Nonetheless, Provider has an ad-hoc solution named `ProxyProvider`, but it's considered tedious and error-prone. +Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead +with simple yet powerful utilites such as [ref.watch] and [ref.listen]. ### Lack of safety. It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. @@ -106,14 +109,8 @@ Both packages fill a similar role. Both try to: - offer a way to mock those objects during tests; - offer a way for Widgets to listen to those objects in a simple way. -At the same time, think of Riverpod as what Provider could've been if -it continued to mature for a few years. - -Riverpod fixes various fundamental problems with Provider, such as but not limited to: +You can think of Riverpod as what Provider could've been if it continued to mature for a few years. -- Significantly simplifying the combination of "providers". - Instead of the tedious and error-prone `ProxyProvider`, Riverpod exposes - simple yet powerful utilites such as [ref.watch] and [ref.listen]. - Allowing multiple "provider" to expose a value of the same type. This removes the need for defining custom classes when exposing a plain `int` or `String` would work just as well. From c68e2130d97280d3a34a9f1d51d5c64015c21975 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:42:29 +0200 Subject: [PATCH 14/38] Testing is tedious --- website/docs/riverpod_for_provider_users.mdx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 06537e720..580eb6d65 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -82,6 +82,12 @@ implies significant memory leaks if some provider state is "dynamically mounted" to a build a Provider, which is exactly what `.family` does. Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. +### Testing is tedious +To be able to write a test, you *have to* re-define providers inside each test. + +With Riverpod, providers are ready to use inside tests, by default. Furthermore, Riverpod exposes a +handy collection of "overriding" utilites that are crucial when mocking Providers. + ### Triggering side effects isn't straightforward Since `InheritedWidget` has no `onChange` callback, Provider can't have one. This is problematic for navigation, such as for snackbars, modals, etc. @@ -111,11 +117,6 @@ Both packages fill a similar role. Both try to: You can think of Riverpod as what Provider could've been if it continued to mature for a few years. -- Allowing multiple "provider" to expose a value of the same type. - This removes the need for defining custom classes when exposing a - plain `int` or `String` would work just as well. -- Removing the need to re-define providers inside tests. - With Riverpod, providers are ready to use inside tests by default. - Reducing the over-reliance on "scoping" to dispose objects by offering alternate ways to dispose objects ([autoDispose]) While powerful, scoping a provider is fairly advanced and hard to get right. From d36bbca7e9231ec16f59c09e3281256109fcf5d7 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:48:53 +0200 Subject: [PATCH 15/38] Finished the introduction section --- website/docs/riverpod_for_provider_users.mdx | 28 ++++++++------------ 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 580eb6d65..1afa0f3ae 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -117,29 +117,23 @@ Both packages fill a similar role. Both try to: You can think of Riverpod as what Provider could've been if it continued to mature for a few years. -- Reducing the over-reliance on "scoping" to dispose objects by offering - alternate ways to dispose objects ([autoDispose]) - While powerful, scoping a provider is fairly advanced and hard to get right. +### The breaking change +The only true downside of Riverpod is that it requires changing the widget type to work: -... And a lot more. +- Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. +- Instead of extending `StatefulWidget`, with Riverpod you should extend `ConsumerStatefulWidget`. -The only true downside of Riverpod is that it requires changing the widget type -to work: +But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. -- Instead of extending `StatelessWidget`, with Riverpod you should extend - `ConsumerWidget`. -- Instead of extending `StatefulWidget`, with Riverpod you should extend - `ConsumerStatefulWidget`. +### Choosing the right library +You're probably asking yourself: +*"So, as a Provider user, should I use Provider or Riverpod?"*. -But this inconvenience is fairly minor in the grand scheme of things. And this -requirement might one day disappear. +We want to answer to this question very clearly: -So to answer the question you're probably asking yourself: -**Should I use Provider or Riverpod?** + You probably should be using Riverpod -You probably should be using Riverpod. -Riverpod is overhaul better designed and could lead to drastic simplifications -of your logic. +Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. ## The difference between Provider and Riverpod From 30bcec1e96d0c83ea0acb607dd3ea90af4d5eb01 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 14:54:34 +0200 Subject: [PATCH 16/38] Improved "same type providers" section --- website/docs/riverpod_for_provider_users.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 1afa0f3ae..73d99494e 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -36,6 +36,10 @@ Declaring two `Provider` will result into unreliable behavior: `InheritedWi obtain only one of the two: the closest `Provider` ancestor. While a [workaround] is explained in Provider's documentation, Riverpod simply doesn't have this problem. +By removing this limitation, we can freely split logic into tiny pieces, like having one +`Provider> itemsProvider` for all items, and use it to create e.g. +`Provider> filteredItemsProvider`. + ### Providers reasonably emit only one value at a time When reading an external RESTful API, it's quite common to show the last read value, while a new requested call loads the next one. From a9498c0aa621ed8a944c1a1912eed3e9c70372e2 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 15:45:58 +0200 Subject: [PATCH 17/38] I don't like this title anymore --- website/docs/riverpod_for_provider_users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 73d99494e..aca231e8f 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -97,7 +97,7 @@ Since `InheritedWidget` has no `onChange` callback, Provider can't have one. This is problematic for navigation, such as for snackbars, modals, etc. Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. -## Riverpod's birth +## Towards Riverpod Originally, a major version of Provider was planned to ship, as a way to solve the aforementioned problems. But it was then decided against it, as this would have been "too breaking" and even controversial, because of the new `ConsumerWidget` API. From 7be7b85b1e4a503b3c7c3706a65e26a879aea1f0 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 15:46:23 +0200 Subject: [PATCH 18/38] Added migration tips --- website/docs/riverpod_for_provider_users.mdx | 58 +++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index aca231e8f..21618bca8 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -139,7 +139,58 @@ We want to answer to this question very clearly: Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. -## The difference between Provider and Riverpod +## Migrating tips +Migrating from Provider to Riverpod can be very straightforward. +Migrating basically consists in a few steps that can be done in an *incremental* way. + +### A gentle start +*If you have an existing app, don't try to migrate all your providers at once.* +Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. + +This will requite a bit of a paradigm shift, so it may be difficult to do initially. +Take your time, as it is important to get yourself familiar with Riverpod first. + +It's fine to keep using `ChangeNotifier` using Riverpod and not use the latest fancy Riverpod features ASAP. + +While you should strive toward moving all your application to Riverpod in the long-run, +**don't burn yourself out**. Do it one provider at a time. + +### Riverpod and Provider can coexist +*Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* + +Indeed, using import aliases, it is possible to use the two APIs altogheter. +This is also great for readibilty and it removes any ambiguous API usage. + +If you plan on doing this, consider using import aliases for each Provider import in your codebase. + +:::info +A full guide onto how to effectively implement import aliases is incoming soon. +::: + +### Migrating one Provider at a time +*All Providers from pkg:provider have a strict equivalent in Riverpod*. + +In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. +This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but +it's a gentle and very easy way to start your migration. + +Thus, migrating consists in moving a `provider.Provider` definition *outside* of `MultiProvider`, +and change it to a `riverpod.Provider` definition (see the differences below). + + +### Code Generation +[Code generation] is the recommended way to use Riverpod the *cutting edge* way. +Even so, don't try using codegen right away. + +Instead, this should be taken as a furhter step forward. + +Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s +in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. + + +## Differences and similarities Provider and Riverpod +This recap should suffice when performing migrations, as it quickly summarizes +defferences and similarities between Provider and Riverpod. ### Defining providers @@ -485,3 +536,8 @@ last value before the provider changed and the new value after it changed. [it turns out]: https://github.com/flutter/flutter/issues/106549 [*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 [integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[Code generation]: /docs/about_code_generation +[AsyncNotifiers]: /docs/providers/notifier_provider +[combining Providers]: /docs/concepts/combining_providers +[global final variable]: /docs/concepts/providers#creating-a-provider From 65171026bfe12d0f87227a2bed81aa5cde5620c8 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 16:00:14 +0200 Subject: [PATCH 19/38] Added a Scoping vs `.family.autoDispose` paragraph --- website/docs/riverpod_for_provider_users.mdx | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 21618bca8..e5e12f270 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -523,6 +523,38 @@ and whenever the provider changes, executes a function. The `previous` and `next` parameters of that function correspond to the last value before the provider changed and the new value after it changed. +### Scoping Providers vs `.family` + `.autoDispose` +In pkg:Provider, scoping was used for two things: + - destroying state when leaving a page + - having custom state per page + +As stated above, using scoping just to destroy state isn't ideal. +The problem is that scoping didn't work well over large applications. +For example, state often is created in one page, but destroyed later in a different page after navigation. +This doesn't allow for multiple caches to be active over different pages. + +Similarly, the "custom state per page" approach quickly becomes difficult to handle if that state +needs to be shared with another part of the widget tree, +like you'd need with modals or a with a multi-step form. + +Riverpod takes a different approach: first, scoping providers is kind of discouraged; second, +`.family` and `.autoDispose` are a complete replacement solution for this. + +Within Riverpod, Providers marked as `.autoDispose` automatically destroy their state when they aren't used anymore. +When the last widget removing a provider is unmounted, Riverpod will detect this and destroy the provider. + +This inherently solves the "destroying state" problem. + +Also it is possible to mark a Provider as `.family` (and, at the same time, as `.autoDispose`). +This enables passing parameters to providers, which make multiple providers to be spawned and tracked internally. +In other words, when passing parameters, *a unique state is created per unique parameter*. + +This solves the "custom state per page" problem. Actually, there's another advantage: such state is no-longer bound to one specific page. +Instead, if a different page tries to access the same state, such page will be able to do so by just reusing the parameters. + +In many ways, passing parameters to providers is equivalent to a Map key. +If the key is the same, the value obtained is the same. If it's a different key, a different state will be obtained. + [provider]: https://pub.dev/packages/provider [ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider [ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change From a4477f1e24861289337eb0f92d151e767c0e7d9c Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Sat, 24 Jun 2023 16:06:48 +0200 Subject: [PATCH 20/38] Typo --- website/docs/riverpod_for_provider_users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index e5e12f270..45fc1c635 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -188,7 +188,7 @@ Once the "base" migration is done, and you're positive that there are no more `C in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. -## Differences and similarities Provider and Riverpod +## Differences and similarities between Provider and Riverpod This recap should suffice when performing migrations, as it quickly summarizes defferences and similarities between Provider and Riverpod. From 33a364bd42bc7777189886eac28f41943fb70073 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 27 Jun 2023 11:31:52 +0200 Subject: [PATCH 21/38] Update website/docs/riverpod_for_provider_users.mdx --- website/docs/riverpod_for_provider_users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/riverpod_for_provider_users.mdx index 45fc1c635..89c1917b4 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/riverpod_for_provider_users.mdx @@ -182,7 +182,7 @@ and change it to a `riverpod.Provider` definition (see the differences below). [Code generation] is the recommended way to use Riverpod the *cutting edge* way. Even so, don't try using codegen right away. -Instead, this should be taken as a furhter step forward. +Instead, this should be taken as a further step forward. Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. From a5dd954a04320bc1e7d830a51d861258aae9baf0 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 29 Jun 2023 01:22:07 +0200 Subject: [PATCH 22/38] Refactoring --- .../differences_and_similarities.mdx} | 185 +----------------- website/docs/from_provider/helpers/item.dart | 12 ++ .../from_provider/helpers/item.freezed.dart | 142 ++++++++++++++ .../docs/from_provider/helpers/item.g.dart | 17 ++ website/docs/from_provider/helpers/json.dart | 1 + website/docs/from_provider/introduction.mdx | 23 +++ website/docs/from_provider/migrating_tips.mdx | 57 ++++++ .../async_values/async_values.dart | 29 +++ .../async_values/async_values.g.dart | 40 ++++ .../async_values/index.tsx | 9 + .../async_values/raw.dart | 25 +++ .../auto_dispose/auto_dispose.dart | 29 +++ .../auto_dispose/auto_dispose.g.dart | 41 ++++ .../auto_dispose/index.tsx | 9 + .../auto_dispose/raw.dart | 24 +++ .../combine/combine.dart | 19 ++ .../combine/combine.g.dart | 40 ++++ .../providers_limitations/combine/index.tsx | 9 + .../providers_limitations/combine/raw.dart | 15 ++ .../providers_limitations/override/index.tsx | 8 + .../override/override.dart | 16 ++ .../providers_limitations.mdx | 137 +++++++++++++ .../providers_limitations/same_type/index.tsx | 9 + .../providers_limitations/same_type/raw.dart | 15 ++ .../same_type/same_type.dart | 19 ++ .../same_type/same_type.g.dart | 41 ++++ .../side_effects/index.tsx | 8 + .../side_effects/side_effects.dart | 20 ++ .../docs/from_provider/towards_riverpod.mdx | 45 +++++ website/sidebars.js | 12 +- 30 files changed, 876 insertions(+), 180 deletions(-) rename website/docs/{riverpod_for_provider_users.mdx => from_provider/differences_and_similarities.mdx} (55%) create mode 100644 website/docs/from_provider/helpers/item.dart create mode 100644 website/docs/from_provider/helpers/item.freezed.dart create mode 100644 website/docs/from_provider/helpers/item.g.dart create mode 100644 website/docs/from_provider/helpers/json.dart create mode 100644 website/docs/from_provider/introduction.mdx create mode 100644 website/docs/from_provider/migrating_tips.mdx create mode 100644 website/docs/from_provider/providers_limitations/async_values/async_values.dart create mode 100644 website/docs/from_provider/providers_limitations/async_values/async_values.g.dart create mode 100644 website/docs/from_provider/providers_limitations/async_values/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/async_values/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart create mode 100644 website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart create mode 100644 website/docs/from_provider/providers_limitations/auto_dispose/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/auto_dispose/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/combine/combine.dart create mode 100644 website/docs/from_provider/providers_limitations/combine/combine.g.dart create mode 100644 website/docs/from_provider/providers_limitations/combine/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/combine/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/override/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/override/override.dart create mode 100644 website/docs/from_provider/providers_limitations/providers_limitations.mdx create mode 100644 website/docs/from_provider/providers_limitations/same_type/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/same_type/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/same_type/same_type.dart create mode 100644 website/docs/from_provider/providers_limitations/same_type/same_type.g.dart create mode 100644 website/docs/from_provider/providers_limitations/side_effects/index.tsx create mode 100644 website/docs/from_provider/providers_limitations/side_effects/side_effects.dart create mode 100644 website/docs/from_provider/towards_riverpod.mdx diff --git a/website/docs/riverpod_for_provider_users.mdx b/website/docs/from_provider/differences_and_similarities.mdx similarity index 55% rename from website/docs/riverpod_for_provider_users.mdx rename to website/docs/from_provider/differences_and_similarities.mdx index 89c1917b4..8f926a37b 100644 --- a/website/docs/riverpod_for_provider_users.mdx +++ b/website/docs/from_provider/differences_and_similarities.mdx @@ -1,194 +1,21 @@ --- -title: Riverpod for Provider users +title: Differences and Similarities between Provider and Riverpod --- import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; +import pubspec from "../getting_started/pubspec"; +import dartHelloWorld from "../getting_started/dart_hello_world"; +import helloWorld from "../getting_started/hello_world"; +import dartPubspec from "../getting_started/dart_pubspec"; import { trimSnippet, AutoSnippet, ConditionalSnippet, -} from "../src/components/CodeSnippet"; - -This article is designed for people familiar with the [Provider] package who -wants to learn about Riverpod. - -Since Provider is widely popular, why would one migrate to Riverpod? - -## Provider's limitations -Provider has fundamental issues due to being restricted by the InheritedWidget API. -Inherently, Provider is a "simpler `InheritedWidget`", or, in other words, -Provider is merely an InheritedWidget wrapper, and thus it's limited by it. - -By the end of this paragraph you should be convinced that Riverpod is to be prefered over Provider. -**Riverpod is indeed a more modern, recommended and reliable approach towards State Management, Caching and Reactivty**, -whereas Provider is currently lacking in many areas with no way forward. +} from "../../src/components/CodeSnippet"; -Here's a list of known Provider issues. -### Provider can't keep two (or more) providers of the same "type" -Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will -obtain only one of the two: the closest `Provider` ancestor. While a [workaround] is explained in Provider's -documentation, Riverpod simply doesn't have this problem. - -By removing this limitation, we can freely split logic into tiny pieces, like having one -`Provider> itemsProvider` for all items, and use it to create e.g. -`Provider> filteredItemsProvider`. - -### Providers reasonably emit only one value at a time -When reading an external RESTful API, it's quite common to show -the last read value, while a new requested call loads the next one. -Riverpod allows this behavior via emitting two values at a time (i.e. a previous data value, -and an incoming new loading value), via `AsyncValue`'s APIs. -With Provider, this isn't as easy, or achievable, even. - -### Combining providers is hard and error prone. -With Provider we may be tempted to use `context.watch` inside provider's `create`. -This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency -has changed (e.g. such as when there's a GlobalKey involved in the widget tree). - -Nonetheless, Provider has an ad-hoc solution named `ProxyProvider`, but it's considered tedious and error-prone. -Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead -with simple yet powerful utilites such as [ref.watch] and [ref.listen]. - -### Lack of safety. -It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. -Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. -Although Riverpod brings much more utility than this, this exception simply can't be thrown. - -### Disposing of state is difficult -`InheritedWidget` [*can't* react when a consumer stops listening to them]. This prevents the ability for Provider -to automatically destroy its providers' state when they're no-longer used. -With Provider, [we have to] rely on scoping providers to dispose the state when it stops being used. -But this isn't easy, as it gets tricky when state is shared between pages. -Riverpod solves this with easy-to-understand APIs such as [autodispose] and [keepAlive]. -Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). -Unluckily, there's no way to implement this with raw `InheritedWidgets`, and thus with Provider. - -### Lack of a reliable parametrization mechanism -Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. -Indeed, `.family` is one of Riverpod's most powerful feature and it is core to its innovations, -e.g. it enables enormous [simplification of logic]. - -If we wanted to implement something similar using Provider, we would have to give -up easiness of use *and* type-safeness on such parameters. - -Furthermore, not being able to implement a similar `.autoDispose` mechanism with Provider -inherently prevents any equivalent implementation of `.family`, [as these two features go hand-in-hand]. - -Finally, as shown before, [it turns out] that widgets *never* stop to listen to an `InheritedWidget`; this -implies significant memory leaks if some provider state is "dynamically mounted", i.e. when using parameters -to a build a Provider, which is exactly what `.family` does. -Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. - -### Testing is tedious -To be able to write a test, you *have to* re-define providers inside each test. - -With Riverpod, providers are ready to use inside tests, by default. Furthermore, Riverpod exposes a -handy collection of "overriding" utilites that are crucial when mocking Providers. - -### Triggering side effects isn't straightforward -Since `InheritedWidget` has no `onChange` callback, Provider can't have one. -This is problematic for navigation, such as for snackbars, modals, etc. -Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. - -## Towards Riverpod -Originally, a major version of Provider was planned to ship, as a way to solve -the aforementioned problems. But it was then decided against it, as this would have been -"too breaking" and even controversial, because of the new `ConsumerWidget` API. -Since Provider is still one of the most used Flutter packages, it was instead decided -to create a separate package, and thus Riverpod was created. - -Creating a separate package enabled: - - Ease of migration for whoever wants to, by also enabling the temporary use of both approaches, *at the same time*; - - Allow folks to stick to Provider if they dislike Riverpod in principle, or if they didn't find it reliable yet; - - Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. - -Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). - -### The relationship between Riverpod and Provider -Still, conceptually, Riverpod and Provider are fairly similar. -Both packages fill a similar role. Both try to: - -- cache and dispose some stateful objects; -- offer a way to mock those objects during tests; -- offer a way for Widgets to listen to those objects in a simple way. - -You can think of Riverpod as what Provider could've been if it continued to mature for a few years. - -### The breaking change -The only true downside of Riverpod is that it requires changing the widget type to work: - -- Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. -- Instead of extending `StatefulWidget`, with Riverpod you should extend `ConsumerStatefulWidget`. - -But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. - -### Choosing the right library -You're probably asking yourself: -*"So, as a Provider user, should I use Provider or Riverpod?"*. - -We want to answer to this question very clearly: - - You probably should be using Riverpod - -Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. - -## Migrating tips -Migrating from Provider to Riverpod can be very straightforward. -Migrating basically consists in a few steps that can be done in an *incremental* way. - -### A gentle start -*If you have an existing app, don't try to migrate all your providers at once.* -Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. - -This will requite a bit of a paradigm shift, so it may be difficult to do initially. -Take your time, as it is important to get yourself familiar with Riverpod first. - -It's fine to keep using `ChangeNotifier` using Riverpod and not use the latest fancy Riverpod features ASAP. - -While you should strive toward moving all your application to Riverpod in the long-run, -**don't burn yourself out**. Do it one provider at a time. - -### Riverpod and Provider can coexist -*Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* - -Indeed, using import aliases, it is possible to use the two APIs altogheter. -This is also great for readibilty and it removes any ambiguous API usage. - -If you plan on doing this, consider using import aliases for each Provider import in your codebase. - -:::info -A full guide onto how to effectively implement import aliases is incoming soon. -::: - -### Migrating one Provider at a time -*All Providers from pkg:provider have a strict equivalent in Riverpod*. - -In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. -This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but -it's a gentle and very easy way to start your migration. - -Thus, migrating consists in moving a `provider.Provider` definition *outside* of `MultiProvider`, -and change it to a `riverpod.Provider` definition (see the differences below). - - -### Code Generation -[Code generation] is the recommended way to use Riverpod the *cutting edge* way. -Even so, don't try using codegen right away. - -Instead, this should be taken as a further step forward. - -Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s -in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. - - -## Differences and similarities between Provider and Riverpod This recap should suffice when performing migrations, as it quickly summarizes defferences and similarities between Provider and Riverpod. diff --git a/website/docs/from_provider/helpers/item.dart b/website/docs/from_provider/helpers/item.dart new file mode 100644 index 000000000..1082ef6f3 --- /dev/null +++ b/website/docs/from_provider/helpers/item.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'json.dart'; + +part 'item.freezed.dart'; +part 'item.g.dart'; + +@freezed +class Item with _$Item { + const factory Item({required int id}) = _Item; + factory Item.fromJson(Json json) => _$ItemFromJson(json); +} diff --git a/website/docs/from_provider/helpers/item.freezed.dart b/website/docs/from_provider/helpers/item.freezed.dart new file mode 100644 index 000000000..c92940ba2 --- /dev/null +++ b/website/docs/from_provider/helpers/item.freezed.dart @@ -0,0 +1,142 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'item.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Item _$ItemFromJson(Map json) { + return _Item.fromJson(json); +} + +/// @nodoc +mixin _$Item { + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ItemCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ItemCopyWith<$Res> { + factory $ItemCopyWith(Item value, $Res Function(Item) then) = + _$ItemCopyWithImpl<$Res, Item>; + @useResult + $Res call({int id}); +} + +/// @nodoc +class _$ItemCopyWithImpl<$Res, $Val extends Item> + implements $ItemCopyWith<$Res> { + _$ItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_ItemCopyWith<$Res> implements $ItemCopyWith<$Res> { + factory _$$_ItemCopyWith(_$_Item value, $Res Function(_$_Item) then) = + __$$_ItemCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id}); +} + +/// @nodoc +class __$$_ItemCopyWithImpl<$Res> extends _$ItemCopyWithImpl<$Res, _$_Item> + implements _$$_ItemCopyWith<$Res> { + __$$_ItemCopyWithImpl(_$_Item _value, $Res Function(_$_Item) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_$_Item( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_Item implements _Item { + const _$_Item({required this.id}); + + factory _$_Item.fromJson(Map json) => _$$_ItemFromJson(json); + + @override + final int id; + + @override + String toString() { + return 'Item(id: $id)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_Item && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_ItemCopyWith<_$_Item> get copyWith => + __$$_ItemCopyWithImpl<_$_Item>(this, _$identity); + + @override + Map toJson() { + return _$$_ItemToJson( + this, + ); + } +} + +abstract class _Item implements Item { + const factory _Item({required final int id}) = _$_Item; + + factory _Item.fromJson(Map json) = _$_Item.fromJson; + + @override + int get id; + @override + @JsonKey(ignore: true) + _$$_ItemCopyWith<_$_Item> get copyWith => throw _privateConstructorUsedError; +} diff --git a/website/docs/from_provider/helpers/item.g.dart b/website/docs/from_provider/helpers/item.g.dart new file mode 100644 index 000000000..0862c694f --- /dev/null +++ b/website/docs/from_provider/helpers/item.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$_Item _$$_ItemFromJson(Map json) => _$_Item( + id: json['id'] as int, + ); + +Map _$$_ItemToJson(_$_Item instance) => { + 'id': instance.id, + }; diff --git a/website/docs/from_provider/helpers/json.dart b/website/docs/from_provider/helpers/json.dart new file mode 100644 index 000000000..17cfb1c01 --- /dev/null +++ b/website/docs/from_provider/helpers/json.dart @@ -0,0 +1 @@ +typedef Json = Map; diff --git a/website/docs/from_provider/introduction.mdx b/website/docs/from_provider/introduction.mdx new file mode 100644 index 000000000..efa66d1da --- /dev/null +++ b/website/docs/from_provider/introduction.mdx @@ -0,0 +1,23 @@ +--- +title: Introduction +--- + + +This section is designed for people familiar with the [Provider] package who +wants to learn about Riverpod. + +In particular, this section should answer the following: + - Since Provider is widely popular, why would one migrate to Riverpod? + - What concrete advantages do I get? + - How can I migrate towards Riverpod? + - Can I migrate incrementally? + - etc. + +By the end of this section you should be convinced that Riverpod is to be prefered over Provider. + +**Riverpod is indeed a more modern, recommended and reliable approach when compared to Provider**. + +Riverpod offers better State Management capabilities, better Caching strategies and a simplified Reactivty model. +Whereas, Provider is currently lacking in many areas with no way forward. + +[provider]: https://pub.dev/packages/provider diff --git a/website/docs/from_provider/migrating_tips.mdx b/website/docs/from_provider/migrating_tips.mdx new file mode 100644 index 000000000..20bedf85c --- /dev/null +++ b/website/docs/from_provider/migrating_tips.mdx @@ -0,0 +1,57 @@ +--- +title: Migrating tips +--- + + +Migrating from Provider to Riverpod can be very straightforward. +Migrating basically consists in a few steps that can be done in an *incremental* way. + +### A gentle start +*If you have an existing app, don't try to migrate all your providers at once.* +Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. + +This will requite a bit of a paradigm shift, so it may be difficult to do initially. +Take your time, as it is important to get yourself familiar with Riverpod first. + +It's fine to keep using `ChangeNotifier` using Riverpod and not use the latest fancy Riverpod features ASAP. + +While you should strive toward moving all your application to Riverpod in the long-run, +**don't burn yourself out**. +Do it one provider at a time. + +### Riverpod and Provider can coexist +*Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* + +Indeed, using import aliases, it is possible to use the two APIs altogheter. +This is also great for readibilty and it removes any ambiguous API usage. + +If you plan on doing this, consider using import aliases for each Provider import in your codebase. + +:::info +A full guide onto how to effectively implement import aliases is incoming soon. +::: + +### Migrating one Provider at a time +*All Providers from pkg:provider have a strict equivalent in Riverpod*. + +In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. +This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but +it's a gentle and very easy way to start your migration. + +Thus, migrating consists in moving a `provider.Provider` definition *outside* of `MultiProvider`, +and change it to a `riverpod.Provider` definition (see the differences below). + + +### Code Generation +[Code generation] is recommended to use Riverpod the *cutting edge* way. +Even so, don't try using codegen right away. + +Instead, this should be taken as a further step forward. + +Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s +in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. + + +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[Code generation]: /docs/about_code_generation +[AsyncNotifiers]: /docs/providers/notifier_provider \ No newline at end of file diff --git a/website/docs/from_provider/providers_limitations/async_values/async_values.dart b/website/docs/from_provider/providers_limitations/async_values/async_values.dart new file mode 100644 index 000000000..802a23150 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/async_values/async_values.dart @@ -0,0 +1,29 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +part 'async_values.g.dart'; + +/* SNIPPET START */ + +@riverpod +Future> itemsApi(ItemsApiRef ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isReloading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart b/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart new file mode 100644 index 000000000..3317e08d0 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'async_values.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsApiHash() => r'b32ccb7b85305e361d8ed752cbe11d9524c96190'; + +/// See also [itemsApi]. +@ProviderFor(itemsApi) +final itemsApiProvider = AutoDisposeFutureProvider>.internal( + itemsApi, + name: r'itemsApiProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsApiHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsApiRef = AutoDisposeFutureProviderRef>; +String _$evenItemsHash() => r'55ae98f9b6108203dfc4a139f1ade9fbd8ba8ddd'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/async_values/index.tsx b/website/docs/from_provider/providers_limitations/async_values/index.tsx new file mode 100644 index 000000000..526f2dffe --- /dev/null +++ b/website/docs/from_provider/providers_limitations/async_values/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_values.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/async_values/raw.dart b/website/docs/from_provider/providers_limitations/async_values/raw.dart new file mode 100644 index 000000000..1ca8987ef --- /dev/null +++ b/website/docs/from_provider/providers_limitations/async_values/raw.dart @@ -0,0 +1,25 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +/* SNIPPET START */ + +final itemsApiProvider = FutureProvider.autoDispose((ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +}); + +final evenItemsProvider = Provider.autoDispose((ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isLoading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart new file mode 100644 index 000000000..616c17079 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart @@ -0,0 +1,29 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ + +// With code gen, .autoDispose is the default +@riverpod +int diceRoll(DiceRollRef ref) { + // Since this provider is .autoDispose, un-listening to it will dispose + // its current exposed state. + // Then, whenever this provider is listened to again, + // a new dice will be rolled and exposed again. + final dice = Random().nextInt(10); + return dice; +} + +@riverpod +int cachedDiceRoll(CachedDiceRollRef ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // The above condition might fail; + // If it doesn't, the following instruction tells the Provider + // to keep its cached state, even when no one listens to it anymore. + ref.keepAlive(); + return coin; +} diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart new file mode 100644 index 000000000..c591a8b94 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$diceRollHash() => r'dfd5ac8b74351a0076da9d131c10277f53ff11b9'; + +/// See also [diceRoll]. +@ProviderFor(diceRoll) +final diceRollProvider = AutoDisposeProvider.internal( + diceRoll, + name: r'diceRollProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$diceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DiceRollRef = AutoDisposeProviderRef; +String _$cachedDiceRollHash() => r'fc31fcb804f10360d75362e56329976343ee7abb'; + +/// See also [cachedDiceRoll]. +@ProviderFor(cachedDiceRoll) +final cachedDiceRollProvider = AutoDisposeProvider.internal( + cachedDiceRoll, + name: r'cachedDiceRollProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$cachedDiceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CachedDiceRollRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/index.tsx b/website/docs/from_provider/providers_limitations/auto_dispose/index.tsx new file mode 100644 index 000000000..6c57cfffd --- /dev/null +++ b/website/docs/from_provider/providers_limitations/auto_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./auto_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/raw.dart b/website/docs/from_provider/providers_limitations/auto_dispose/raw.dart new file mode 100644 index 000000000..d5961ad90 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/auto_dispose/raw.dart @@ -0,0 +1,24 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final diceRollProvider = Provider.autoDispose((ref) { + // Since this provider is .autoDispose, un-listening to it will dispose + // its current exposed state. + // Then, whenever this provider is listened to again, + // a new dice will be rolled and exposed again. + final dice = Random().nextInt(10); + return dice.isEven; +}); + +final cachedDiceRollProvider = Provider.autoDispose((ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // The above condition might fail; + // If it doesn't, the following instruction tells the Provider + // to keep its cached state, *even when no one listens to it anymore*. + ref.keepAlive(); + return coin.isEven; +}); diff --git a/website/docs/from_provider/providers_limitations/combine/combine.dart b/website/docs/from_provider/providers_limitations/combine/combine.dart new file mode 100644 index 000000000..ecd1915da --- /dev/null +++ b/website/docs/from_provider/providers_limitations/combine/combine.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'combine.g.dart'; + +/* SNIPPET START */ + +@riverpod +int number(NumberRef ref) { + return Random().nextInt(10); +} + +@riverpod +int doubled(DoubledRef ref) { + final number = ref.watch(numberProvider); + + return number * 2; +} diff --git a/website/docs/from_provider/providers_limitations/combine/combine.g.dart b/website/docs/from_provider/providers_limitations/combine/combine.g.dart new file mode 100644 index 000000000..79038d9ec --- /dev/null +++ b/website/docs/from_provider/providers_limitations/combine/combine.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'combine.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$numberHash() => r'725e25be57b9cc2bd914752f156e26a214596b63'; + +/// See also [number]. +@ProviderFor(number) +final numberProvider = AutoDisposeProvider.internal( + number, + name: r'numberProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$numberHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef NumberRef = AutoDisposeProviderRef; +String _$doubledHash() => r'ddc640c876bdbe49fe72fe1632b5ff48687c9279'; + +/// See also [doubled]. +@ProviderFor(doubled) +final doubledProvider = AutoDisposeProvider.internal( + doubled, + name: r'doubledProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$doubledHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DoubledRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/combine/index.tsx b/website/docs/from_provider/providers_limitations/combine/index.tsx new file mode 100644 index 000000000..2ff7dfbaa --- /dev/null +++ b/website/docs/from_provider/providers_limitations/combine/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./combine.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/combine/raw.dart b/website/docs/from_provider/providers_limitations/combine/raw.dart new file mode 100644 index 000000000..ad33636e7 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/combine/raw.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final numberProvider = Provider.autoDispose((ref) { + return Random().nextInt(10); +}); + +final doubledProvider = Provider.autoDispose((ref) { + final number = ref.watch(numberProvider); + + return number * 2; +}); diff --git a/website/docs/from_provider/providers_limitations/override/index.tsx b/website/docs/from_provider/providers_limitations/override/index.tsx new file mode 100644 index 000000000..719434cf7 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/override/index.tsx @@ -0,0 +1,8 @@ +import codegen from "!!raw-loader!./override.dart"; + +export default { + codegen, + hooks: codegen, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/override/override.dart b/website/docs/from_provider/providers_limitations/override/override.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/override/override.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/docs/from_provider/providers_limitations/providers_limitations.mdx b/website/docs/from_provider/providers_limitations/providers_limitations.mdx new file mode 100644 index 000000000..294c3e8e0 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/providers_limitations.mdx @@ -0,0 +1,137 @@ +--- +title: Provider's limitations +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import sameType from "./same_type"; +import combine from "./combine"; +import asyncValues from "./async_values"; +import autoDispose from "./auto_dispose"; +import override from "./override"; +import sideEffects from "./side_effects"; +import { AutoSnippet } from "../../../src/components/CodeSnippet"; + + + +Provider has fundamental issues due to being restricted by the InheritedWidget API. +Inherently, Provider is a "simpler `InheritedWidget`", or, in other words, +Provider is merely an InheritedWidget wrapper, and thus it's limited by it. + +Here's a list of known Provider issues. + +### Provider can't keep two (or more) providers of the same "type" +Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will +obtain only one of the two: the closest `Provider` ancestor. +While a [workaround] is explained in Provider's +documentation, Riverpod simply doesn't have this problem. + +By removing this limitation, we can freely split logic into tiny pieces, like so: + + + + +### Providers reasonably emit only one value at a time +When reading an external RESTful API, it's quite common to show +the last read value, while a new requested call loads the next one. +Riverpod allows this behavior via emitting two values at a time (i.e. a previous data value, +and an incoming new loading value), via `AsyncValue`'s APIs: + + + +In the previous snippet, anyone watching `evenOnlyItemsProvider` will obtain the following behavior: +1. Initially, the request is being made. We obtain an empty list; +2. Then, say an error occurs. We obtain the default value defined there; +3. Then, we retry the request with a pull-to-refresh logic (via `ref.invalidate`); +4. While we reload the provider, *the default "error" value is being kept*. At this moment in time, two values cohexist within a single `AsyncValue`; +5. This time, some parsed data is received correctly: even items only are correctly returned. + +With Provider, the above features aren't remotely achievable, and even less easy to workaround. + +### Combining providers is hard and error prone +With Provider we may be tempted to use `context.watch` inside provider's `create`. +This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency +has changed (e.g. such as when there's a GlobalKey involved in the widget tree). + +Nonetheless, Provider has an ad-hoc solution named `ProxyProvider`, but it's considered tedious and error-prone. +Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead +with simple yet powerful utilites such as [ref.watch] and [ref.listen]: + + + +Combining values feels natural with Riverpod: dependencies are readable and +there's no different syntax or API to handle this common use case. + + +### Lack of safety +It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. +Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. +Although Riverpod brings much more utility than this, this exception simply can't be thrown. + +### Disposing of state is difficult +`InheritedWidget` [*can't* react when a consumer stops listening to them]. +This prevents the ability for Provider +to automatically destroy its providers' state when they're no-longer used. +With Provider, [we have to] rely on scoping providers to dispose the state when it stops being used. +But this isn't easy, as it gets tricky when state is shared between pages. + +Riverpod solves this with easy-to-understand APIs such as [autodispose] and [keepAlive]: + + + + +Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). +Unluckily, there's no way to implement this with a raw `InheritedWidget`, and thus with Provider. + +### Lack of a reliable parametrization mechanism +Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. +Indeed, `.family` is one of Riverpod's most powerful feature and it is core to its innovations, +e.g. it enables enormous [simplification of logic]. + +If we wanted to implement something similar using Provider, we would have to give +up easiness of use *and* type-safeness on such parameters. + +Furthermore, not being able to implement a similar `.autoDispose` mechanism with Provider +inherently prevents any equivalent implementation of `.family`, [as these two features go hand-in-hand]. + +Finally, as shown before, [it turns out] that widgets *never* stop to listen to an `InheritedWidget`. +This implies significant memory leaks if some provider state is "dynamically mounted", i.e. when using parameters +to a build a Provider, which is exactly what `.family` does. +Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. + +### Testing is tedious +To be able to write a test, you *have to* re-define providers inside each test. + +With Riverpod, providers are ready to use inside tests, by default. Furthermore, Riverpod exposes a +handy collection of "overriding" utilites that are crucial when mocking Providers. + +Testing the combined state snippet above would be as simple as the following: + + + +For more info about testing, see [Testing]. + + +### Triggering side effects isn't straightforward +Since `InheritedWidget` has no `onChange` callback, Provider can't have one. +This is problematic for navigation, such as for snackbars, modals, etc. + +Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutter]. + + + + +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[as these two features go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[simplification of logic]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 +[Testing]: /docs/cookbooks/testing +[integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/docs/from_provider/providers_limitations/same_type/index.tsx b/website/docs/from_provider/providers_limitations/same_type/index.tsx new file mode 100644 index 000000000..8569e8316 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/same_type/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./same_type.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/same_type/raw.dart b/website/docs/from_provider/providers_limitations/same_type/raw.dart new file mode 100644 index 000000000..877e76e93 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/same_type/raw.dart @@ -0,0 +1,15 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +/* SNIPPET START */ + +final itemsProvider = Provider.autoDispose( + (ref) => [], // ... +); + +final evenOnlyItemsProvider = Provider.autoDispose((ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.dart b/website/docs/from_provider/providers_limitations/same_type/same_type.dart new file mode 100644 index 000000000..38cf161a2 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/same_type/same_type.dart @@ -0,0 +1,19 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +part 'same_type.g.dart'; + +/* SNIPPET START */ + +@riverpod +List items(ItemsRef ref) { + return []; // ... +} + +@riverpod +List evenOnlyItems(EvenOnlyItemsRef ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart new file mode 100644 index 000000000..10a6b364c --- /dev/null +++ b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'same_type.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsHash() => r'f0a8fa6874f4868db9ead31e82c75d976f9d2033'; + +/// See also [items]. +@ProviderFor(items) +final itemsProvider = AutoDisposeProvider>.internal( + items, + name: r'itemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsRef = AutoDisposeProviderRef>; +String _$evenOnlyItemsHash() => r'98a0df4a53b29869bc0c1ae583c2129f27c03525'; + +/// See also [evenOnlyItems]. +@ProviderFor(evenOnlyItems) +final evenOnlyItemsProvider = AutoDisposeProvider>.internal( + evenOnlyItems, + name: r'evenOnlyItemsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$evenOnlyItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenOnlyItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/side_effects/index.tsx b/website/docs/from_provider/providers_limitations/side_effects/index.tsx new file mode 100644 index 000000000..b9a06319c --- /dev/null +++ b/website/docs/from_provider/providers_limitations/side_effects/index.tsx @@ -0,0 +1,8 @@ +import codegen from "!!raw-loader!./side_effects.dart"; + +export default { + codegen, + hooks: codegen, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart b/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart new file mode 100644 index 000000000..b1daf684c --- /dev/null +++ b/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return Container(); + } +} diff --git a/website/docs/from_provider/towards_riverpod.mdx b/website/docs/from_provider/towards_riverpod.mdx new file mode 100644 index 000000000..8dbfcb548 --- /dev/null +++ b/website/docs/from_provider/towards_riverpod.mdx @@ -0,0 +1,45 @@ +--- +title: Towards Riverpod +--- + + +Originally, a major version of Provider was planned to ship, as a way to solve +the aforementioned problems. But it was then decided against it, as this would have been +"too breaking" and even controversial, because of the new `ConsumerWidget` API. +Since Provider is still one of the most used Flutter packages, it was instead decided +to create a separate package, and thus Riverpod was created. + +Creating a separate package enabled: + - Ease of migration for whoever wants to, by also enabling the temporary use of both approaches, *at the same time*; + - Allow folks to stick to Provider if they dislike Riverpod in principle, or if they didn't find it reliable yet; + - Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. + +Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). + +### The relationship between Riverpod and Provider +Still, conceptually, Riverpod and Provider are fairly similar. +Both packages fill a similar role. Both try to: + +- cache and dispose some stateful objects; +- offer a way to mock those objects during tests; +- offer a way for Widgets to listen to those objects in a simple way. + +You can think of Riverpod as what Provider could've been if it continued to mature for a few years. + +### The breaking change +The only true downside of Riverpod is that it requires changing the widget type to work: + +- Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. +- Instead of extending `StatefulWidget`, with Riverpod you should extend `ConsumerStatefulWidget`. + +But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. + +### Choosing the right library +You're probably asking yourself: +*"So, as a Provider user, should I use Provider or Riverpod?"*. + +We want to answer to this question very clearly: + + You probably should be using Riverpod + +Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. diff --git a/website/sidebars.js b/website/sidebars.js index 119c35971..17d9b5c8d 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -2,7 +2,17 @@ module.exports = { Sidebar: [ "introduction", "getting_started", - "riverpod_for_provider_users", + { + type: "category", + label: "Riverpod for Provider Users", + items: [ + "from_provider/introduction", + "from_provider/providers_limitations/providers_limitations", + "from_provider/towards_riverpod", + "from_provider/migrating_tips", + "from_provider/differences_and_similarities", + ], + }, "about_code_generation", "about_hooks", { From 0c4b9234efe88c0d27d19e23fc046396399e12d8 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 29 Jun 2023 02:04:21 +0200 Subject: [PATCH 23/38] One last dance --- .../differences_and_similarities.mdx | 54 ++++---- website/docs/from_provider/family/family.dart | 11 ++ .../docs/from_provider/family/family.g.dart | 121 ++++++++++++++++++ website/docs/from_provider/family/index.tsx | 9 ++ website/docs/from_provider/family/raw.dart | 10 ++ website/docs/from_provider/migrating_tips.mdx | 8 +- .../providers_limitations/override/index.tsx | 5 +- .../providers_limitations/override/raw.dart | 16 +++ .../providers_limitations.mdx | 66 +++++----- .../providers_limitations/same_type/raw.dart | 2 +- .../same_type/same_type.dart | 2 +- .../same_type/same_type.g.dart | 21 ++- .../side_effects/index.tsx | 5 +- .../side_effects/raw.dart | 24 ++++ .../side_effects/side_effects.dart | 6 +- .../docs/from_provider/towards_riverpod.mdx | 11 +- 16 files changed, 291 insertions(+), 80 deletions(-) create mode 100644 website/docs/from_provider/family/family.dart create mode 100644 website/docs/from_provider/family/family.g.dart create mode 100644 website/docs/from_provider/family/index.tsx create mode 100644 website/docs/from_provider/family/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/override/raw.dart create mode 100644 website/docs/from_provider/providers_limitations/side_effects/raw.dart diff --git a/website/docs/from_provider/differences_and_similarities.mdx b/website/docs/from_provider/differences_and_similarities.mdx index 8f926a37b..983adfd43 100644 --- a/website/docs/from_provider/differences_and_similarities.mdx +++ b/website/docs/from_provider/differences_and_similarities.mdx @@ -5,10 +5,7 @@ title: Differences and Similarities between Provider and Riverpod import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import pubspec from "../getting_started/pubspec"; -import dartHelloWorld from "../getting_started/dart_hello_world"; -import helloWorld from "../getting_started/hello_world"; -import dartPubspec from "../getting_started/dart_pubspec"; +import family from "./family"; import { trimSnippet, AutoSnippet, @@ -19,7 +16,7 @@ import { This recap should suffice when performing migrations, as it quickly summarizes defferences and similarities between Provider and Riverpod. -### Defining providers +## Defining providers The primary difference between both packages is how "providers" are defined. @@ -73,7 +70,7 @@ Riverpod without Flutter. For example, Riverpod can be used to write command line applications. ::: -### Reading providers: BuildContext +## Reading providers: BuildContext With Provider, one way of reading providers is to use a Widget's `BuildContext`. @@ -135,7 +132,7 @@ Inside the `build` method, use "watch". Inside click handlers and other events, use "read". ::: -### Reading providers: Consumer +## Reading providers: Consumer Provider optionally comes with a widget named `Consumer` (and variants such as `Consumer2`) for reading providers. @@ -182,7 +179,7 @@ Consumer( Notice how `Consumer` gives us a `WidgetRef` object. This is the same object as we saw in the previous part related to `ConsumerWidget`. -### Combining providers: ProxyProvider with stateless objects +## Combining providers: ProxyProvider with stateless objects When using Provider, the official way of combining providers is using the `ProxyProvider` widget (or variants such as `ProxyProvider2`). @@ -250,7 +247,7 @@ when explaining [how to read providers inside widgets](#reading-providers-buildc Indeed, providers are now able to listen to other providers in the same way that widgets do. -### Combining providers: ProxyProvider with stateful objects +## Combining providers: ProxyProvider with stateful objects When combining providers, another alternative use-case is to expose stateful objects, such as a `ChangeNotifier` instance. @@ -350,36 +347,49 @@ and whenever the provider changes, executes a function. The `previous` and `next` parameters of that function correspond to the last value before the provider changed and the new value after it changed. -### Scoping Providers vs `.family` + `.autoDispose` +## Scoping Providers vs `.family` + `.autoDispose` In pkg:Provider, scoping was used for two things: - destroying state when leaving a page - having custom state per page -As stated above, using scoping just to destroy state isn't ideal. -The problem is that scoping didn't work well over large applications. -For example, state often is created in one page, but destroyed later in a different page after navigation. +Using scoping just to destroy state isn't ideal. +The problem is that scoping doesn't work well over large applications. +For example, state often is created in one page, but destroyed later in a different page after navigation. This doesn't allow for multiple caches to be active over different pages. Similarly, the "custom state per page" approach quickly becomes difficult to handle if that state needs to be shared with another part of the widget tree, like you'd need with modals or a with a multi-step form. -Riverpod takes a different approach: first, scoping providers is kind of discouraged; second, +Riverpod takes a different approach: first, scoping providers is kind-of discouraged; second, `.family` and `.autoDispose` are a complete replacement solution for this. -Within Riverpod, Providers marked as `.autoDispose` automatically destroy their state when they aren't used anymore. -When the last widget removing a provider is unmounted, Riverpod will detect this and destroy the provider. +Within Riverpod, Providers marked as `.autoDispose` automatically destroy their state when they aren't used anymore. +When the last widget removing a provider is unmounted, Riverpod will detect this and destroy the provider. +Try using these two lifecycle methods in a provider to test this behavior: + +```dart +ref.onCancel((){ + print("No one listens to me anymore!"); +}); +ref.onDispose((){ + print("If I've been defined as `.autoDispose`, I just got disposed!"); +}); +``` This inherently solves the "destroying state" problem. -Also it is possible to mark a Provider as `.family` (and, at the same time, as `.autoDispose`). -This enables passing parameters to providers, which make multiple providers to be spawned and tracked internally. -In other words, when passing parameters, *a unique state is created per unique parameter*. +Also it is possible to mark a Provider as `.family` (and, at the same time, as `.autoDispose`). +This enables passing parameters to providers, which make multiple providers to be spawned and tracked internally. +In other words, when passing parameters, *a unique state is created per unique parameter*. -This solves the "custom state per page" problem. Actually, there's another advantage: such state is no-longer bound to one specific page. -Instead, if a different page tries to access the same state, such page will be able to do so by just reusing the parameters. + -In many ways, passing parameters to providers is equivalent to a Map key. + +This solves the "custom state per page" problem. Actually, there's another advantage: such state is no-longer bound to one specific page. +Instead, if a different page tries to access the same state, such page will be able to do so by just reusing the parameters. + +In many ways, passing parameters to providers is equivalent to a Map key. If the key is the same, the value obtained is the same. If it's a different key, a different state will be obtained. [provider]: https://pub.dev/packages/provider diff --git a/website/docs/from_provider/family/family.dart b/website/docs/from_provider/family/family.dart new file mode 100644 index 000000000..4d49c552a --- /dev/null +++ b/website/docs/from_provider/family/family.dart @@ -0,0 +1,11 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; +/* SNIPPET START */ + +@riverpod +int random(RandomRef ref, {required int seed, required int max}) { + return Random(seed).nextInt(max); +} diff --git a/website/docs/from_provider/family/family.g.dart b/website/docs/from_provider/family/family.g.dart new file mode 100644 index 000000000..4b393cc70 --- /dev/null +++ b/website/docs/from_provider/family/family.g.dart @@ -0,0 +1,121 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$randomHash() => r'517b12aad4df7b31f8872b89af74e7880377b2ea'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +typedef RandomRef = AutoDisposeProviderRef; + +/// See also [random]. +@ProviderFor(random) +const randomProvider = RandomFamily(); + +/// See also [random]. +class RandomFamily extends Family { + /// See also [random]. + const RandomFamily(); + + /// See also [random]. + RandomProvider call({ + required int seed, + required int max, + }) { + return RandomProvider( + seed: seed, + max: max, + ); + } + + @override + RandomProvider getProviderOverride( + covariant RandomProvider provider, + ) { + return call( + seed: provider.seed, + max: provider.max, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'randomProvider'; +} + +/// See also [random]. +class RandomProvider extends AutoDisposeProvider { + /// See also [random]. + RandomProvider({ + required this.seed, + required this.max, + }) : super.internal( + (ref) => random( + ref, + seed: seed, + max: max, + ), + from: randomProvider, + name: r'randomProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$randomHash, + dependencies: RandomFamily._dependencies, + allTransitiveDependencies: RandomFamily._allTransitiveDependencies, + ); + + final int seed; + final int max; + + @override + bool operator ==(Object other) { + return other is RandomProvider && other.seed == seed && other.max == max; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, seed.hashCode); + hash = _SystemHash.combine(hash, max.hashCode); + + return _SystemHash.finish(hash); + } +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/family/index.tsx b/website/docs/from_provider/family/index.tsx new file mode 100644 index 000000000..fa391f61a --- /dev/null +++ b/website/docs/from_provider/family/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/docs/from_provider/family/raw.dart b/website/docs/from_provider/family/raw.dart new file mode 100644 index 000000000..b363a0af8 --- /dev/null +++ b/website/docs/from_provider/family/raw.dart @@ -0,0 +1,10 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +typedef ParamsType = ({int seed, int max}); + +final randomProvider = Provider.family.autoDispose((ref, params) { + return Random(params.seed).nextInt(params.max); +}); diff --git a/website/docs/from_provider/migrating_tips.mdx b/website/docs/from_provider/migrating_tips.mdx index 20bedf85c..776abda30 100644 --- a/website/docs/from_provider/migrating_tips.mdx +++ b/website/docs/from_provider/migrating_tips.mdx @@ -6,7 +6,7 @@ title: Migrating tips Migrating from Provider to Riverpod can be very straightforward. Migrating basically consists in a few steps that can be done in an *incremental* way. -### A gentle start +## A gentle start *If you have an existing app, don't try to migrate all your providers at once.* Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. @@ -19,7 +19,7 @@ While you should strive toward moving all your application to Riverpod in the lo **don't burn yourself out**. Do it one provider at a time. -### Riverpod and Provider can coexist +## Riverpod and Provider can coexist *Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* Indeed, using import aliases, it is possible to use the two APIs altogheter. @@ -31,7 +31,7 @@ If you plan on doing this, consider using import aliases for each Provider impor A full guide onto how to effectively implement import aliases is incoming soon. ::: -### Migrating one Provider at a time +## Migrating one Provider at a time *All Providers from pkg:provider have a strict equivalent in Riverpod*. In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. @@ -42,7 +42,7 @@ Thus, migrating consists in moving a `provider.Provider` definition *outside* of and change it to a `riverpod.Provider` definition (see the differences below). -### Code Generation +## Code Generation [Code generation] is recommended to use Riverpod the *cutting edge* way. Even so, don't try using codegen right away. diff --git a/website/docs/from_provider/providers_limitations/override/index.tsx b/website/docs/from_provider/providers_limitations/override/index.tsx index 719434cf7..43ec56b51 100644 --- a/website/docs/from_provider/providers_limitations/override/index.tsx +++ b/website/docs/from_provider/providers_limitations/override/index.tsx @@ -1,8 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; import codegen from "!!raw-loader!./override.dart"; export default { - codegen, - hooks: codegen, + raw, + hooks: raw, codegen, hooksCodegen: codegen, }; diff --git a/website/docs/from_provider/providers_limitations/override/raw.dart b/website/docs/from_provider/providers_limitations/override/raw.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/override/raw.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/docs/from_provider/providers_limitations/providers_limitations.mdx b/website/docs/from_provider/providers_limitations/providers_limitations.mdx index 294c3e8e0..260a4c942 100644 --- a/website/docs/from_provider/providers_limitations/providers_limitations.mdx +++ b/website/docs/from_provider/providers_limitations/providers_limitations.mdx @@ -11,19 +11,22 @@ import asyncValues from "./async_values"; import autoDispose from "./auto_dispose"; import override from "./override"; import sideEffects from "./side_effects"; -import { AutoSnippet } from "../../../src/components/CodeSnippet"; - +import { + trimSnippet, + AutoSnippet, + ConditionalSnippet, +} from "../../../src/components/CodeSnippet"; Provider has fundamental issues due to being restricted by the InheritedWidget API. -Inherently, Provider is a "simpler `InheritedWidget`", or, in other words, +Inherently, Provider is a "simpler `InheritedWidget`"; Provider is merely an InheritedWidget wrapper, and thus it's limited by it. Here's a list of known Provider issues. -### Provider can't keep two (or more) providers of the same "type" -Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will -obtain only one of the two: the closest `Provider` ancestor. +## Provider can't keep two (or more) providers of the same "type" +Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will +obtain only *one of the two*: the closest `Provider` ancestor. While a [workaround] is explained in Provider's documentation, Riverpod simply doesn't have this problem. @@ -32,59 +35,60 @@ By removing this limitation, we can freely split logic into tiny pieces, like so -### Providers reasonably emit only one value at a time +## Providers reasonably emit only one value at a time When reading an external RESTful API, it's quite common to show -the last read value, while a new requested call loads the next one. +the last read value, while a new call loads the next one. Riverpod allows this behavior via emitting two values at a time (i.e. a previous data value, -and an incoming new loading value), via `AsyncValue`'s APIs: +and an incoming new loading value), via its `AsyncValue`'s APIs: -In the previous snippet, anyone watching `evenOnlyItemsProvider` will obtain the following behavior: +In the previous snippet, watching `evenItemsProvider` will produce the following effects: 1. Initially, the request is being made. We obtain an empty list; -2. Then, say an error occurs. We obtain the default value defined there; -3. Then, we retry the request with a pull-to-refresh logic (via `ref.invalidate`); -4. While we reload the provider, *the default "error" value is being kept*. At this moment in time, two values cohexist within a single `AsyncValue`; -5. This time, some parsed data is received correctly: even items only are correctly returned. +2. Then, say an error occurs. We obtain `[Item(id: -1)]`; +3. Then, we retry the request with a pull-to-refresh logic (e.g. via `ref.invalidate`); +4. While we reload the first provider, the second one still exposes `[Item(id: -1)]`; +5. This time, some parsed data is received correctly: our even items are correctly returned. With Provider, the above features aren't remotely achievable, and even less easy to workaround. -### Combining providers is hard and error prone +## Combining providers is hard and error prone With Provider we may be tempted to use `context.watch` inside provider's `create`. This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency has changed (e.g. such as when there's a GlobalKey involved in the widget tree). -Nonetheless, Provider has an ad-hoc solution named `ProxyProvider`, but it's considered tedious and error-prone. -Instead, combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead +Nonetheless, Provider has an ad-hoc solution named `ProxyProvider`, but it's considered tedious and error-prone. + +Combining state is a core Riverpod mechanism, as we can combine and cache values reactively with zero overhead with simple yet powerful utilites such as [ref.watch] and [ref.listen]: -Combining values feels natural with Riverpod: dependencies are readable and -there's no different syntax or API to handle this common use case. +Combining values feels natural with Riverpod: dependencies are readable and the APIs remain the same. + +## Lack of safety +With Provider, it's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. +Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. -### Lack of safety -It's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. -Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. -Although Riverpod brings much more utility than this, this exception simply can't be thrown. +Although it brings much more utility than this, Riverpod simply can't throw this exception. -### Disposing of state is difficult -`InheritedWidget` [*can't* react when a consumer stops listening to them]. +## Disposing of state is difficult +`InheritedWidget` [can't react when a consumer stops listening to them]. This prevents the ability for Provider to automatically destroy its providers' state when they're no-longer used. With Provider, [we have to] rely on scoping providers to dispose the state when it stops being used. But this isn't easy, as it gets tricky when state is shared between pages. -Riverpod solves this with easy-to-understand APIs such as [autodispose] and [keepAlive]: +Riverpod solves this with easy-to-understand APIs such as [autodispose] and [keepAlive]. +These two APIs enable flexible and creative caching strategies (e.g. time-based caching): -Furthermore, these two APIs enable flexible and creative caching strategies (e.g. time-based caching). Unluckily, there's no way to implement this with a raw `InheritedWidget`, and thus with Provider. -### Lack of a reliable parametrization mechanism +## Lack of a reliable parametrization mechanism Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. Indeed, `.family` is one of Riverpod's most powerful feature and it is core to its innovations, e.g. it enables enormous [simplification of logic]. @@ -100,7 +104,7 @@ This implies significant memory leaks if some provider state is "dynamically mou to a build a Provider, which is exactly what `.family` does. Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. -### Testing is tedious +## Testing is tedious To be able to write a test, you *have to* re-define providers inside each test. With Riverpod, providers are ready to use inside tests, by default. Furthermore, Riverpod exposes a @@ -113,7 +117,7 @@ Testing the combined state snippet above would be as simple as the following: For more info about testing, see [Testing]. -### Triggering side effects isn't straightforward +## Triggering side effects isn't straightforward Since `InheritedWidget` has no `onChange` callback, Provider can't have one. This is problematic for navigation, such as for snackbars, modals, etc. @@ -132,6 +136,6 @@ Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutte [simplification of logic]: /docs/concepts/modifiers/family#usage [we have to]: https://github.com/flutter/flutter/issues/128432 [it turns out]: https://github.com/flutter/flutter/issues/106549 -[*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 +[can't react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 [Testing]: /docs/cookbooks/testing [integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/docs/from_provider/providers_limitations/same_type/raw.dart b/website/docs/from_provider/providers_limitations/same_type/raw.dart index 877e76e93..dacfe9b9d 100644 --- a/website/docs/from_provider/providers_limitations/same_type/raw.dart +++ b/website/docs/from_provider/providers_limitations/same_type/raw.dart @@ -9,7 +9,7 @@ final itemsProvider = Provider.autoDispose( (ref) => [], // ... ); -final evenOnlyItemsProvider = Provider.autoDispose((ref) { +final evenItemsProvider = Provider.autoDispose((ref) { final items = ref.watch(itemsProvider); return [...items.whereIndexed((index, element) => index.isEven)]; }); diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.dart b/website/docs/from_provider/providers_limitations/same_type/same_type.dart index 38cf161a2..94a4ab086 100644 --- a/website/docs/from_provider/providers_limitations/same_type/same_type.dart +++ b/website/docs/from_provider/providers_limitations/same_type/same_type.dart @@ -13,7 +13,7 @@ List items(ItemsRef ref) { } @riverpod -List evenOnlyItems(EvenOnlyItemsRef ref) { +List evenItems(EvenItemsRef ref) { final items = ref.watch(itemsProvider); return [...items.whereIndexed((index, element) => index.isEven)]; } diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart index 10a6b364c..8b60af905 100644 --- a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart +++ b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart @@ -22,20 +22,19 @@ final itemsProvider = AutoDisposeProvider>.internal( ); typedef ItemsRef = AutoDisposeProviderRef>; -String _$evenOnlyItemsHash() => r'98a0df4a53b29869bc0c1ae583c2129f27c03525'; - -/// See also [evenOnlyItems]. -@ProviderFor(evenOnlyItems) -final evenOnlyItemsProvider = AutoDisposeProvider>.internal( - evenOnlyItems, - name: r'evenOnlyItemsProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$evenOnlyItemsHash, +String _$evenItemsHash() => r'82b4525e91604745f2b4664531b32d4aff5717d4'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, dependencies: null, allTransitiveDependencies: null, ); -typedef EvenOnlyItemsRef = AutoDisposeProviderRef>; +typedef EvenItemsRef = AutoDisposeProviderRef>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/side_effects/index.tsx b/website/docs/from_provider/providers_limitations/side_effects/index.tsx index b9a06319c..f4797a94f 100644 --- a/website/docs/from_provider/providers_limitations/side_effects/index.tsx +++ b/website/docs/from_provider/providers_limitations/side_effects/index.tsx @@ -1,8 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; import codegen from "!!raw-loader!./side_effects.dart"; export default { - codegen, - hooks: codegen, + raw, + hooks: raw, codegen, hooksCodegen: codegen, }; diff --git a/website/docs/from_provider/providers_limitations/side_effects/raw.dart b/website/docs/from_provider/providers_limitations/side_effects/raw.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/docs/from_provider/providers_limitations/side_effects/raw.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart b/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart index b1daf684c..61f016870 100644 --- a/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart +++ b/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart @@ -15,6 +15,10 @@ class DiceRollWidget extends ConsumerWidget { SnackBar(content: Text('Dice roll! We got: $next')), ); }); - return Container(); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); } } diff --git a/website/docs/from_provider/towards_riverpod.mdx b/website/docs/from_provider/towards_riverpod.mdx index 8dbfcb548..2201878e5 100644 --- a/website/docs/from_provider/towards_riverpod.mdx +++ b/website/docs/from_provider/towards_riverpod.mdx @@ -4,8 +4,9 @@ title: Towards Riverpod Originally, a major version of Provider was planned to ship, as a way to solve -the aforementioned problems. But it was then decided against it, as this would have been -"too breaking" and even controversial, because of the new `ConsumerWidget` API. +the aforementioned problems. +But it was then decided against it, as this would have been +"too breaking" and even controversial, because of the new `ConsumerWidget` API. Since Provider is still one of the most used Flutter packages, it was instead decided to create a separate package, and thus Riverpod was created. @@ -16,7 +17,7 @@ Creating a separate package enabled: Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). -### The relationship between Riverpod and Provider +## The relationship between Riverpod and Provider Still, conceptually, Riverpod and Provider are fairly similar. Both packages fill a similar role. Both try to: @@ -26,7 +27,7 @@ Both packages fill a similar role. Both try to: You can think of Riverpod as what Provider could've been if it continued to mature for a few years. -### The breaking change +## The breaking change The only true downside of Riverpod is that it requires changing the widget type to work: - Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. @@ -34,7 +35,7 @@ The only true downside of Riverpod is that it requires changing the widget type But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. -### Choosing the right library +## Choosing the right library You're probably asking yourself: *"So, as a Provider user, should I use Provider or Riverpod?"*. From c4ed0925240aef96ffa38e9adeaa71e2ddb25336 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 29 Jun 2023 13:00:42 +0200 Subject: [PATCH 24/38] Fixed lints and compilation errors Looks like I can't use Dart 3 either --- .../docs/from_provider/family/family.g.dart | 1 - website/docs/from_provider/family/raw.dart | 21 +++++++++++++++++-- .../async_values/async_values.g.dart | 1 - .../auto_dispose/auto_dispose.g.dart | 1 - .../combine/combine.g.dart | 1 - .../same_type/same_type.g.dart | 1 - 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/website/docs/from_provider/family/family.g.dart b/website/docs/from_provider/family/family.g.dart index 4b393cc70..5f78a1bc0 100644 --- a/website/docs/from_provider/family/family.g.dart +++ b/website/docs/from_provider/family/family.g.dart @@ -117,5 +117,4 @@ class RandomProvider extends AutoDisposeProvider { return _SystemHash.finish(hash); } } -// ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/family/raw.dart b/website/docs/from_provider/family/raw.dart index b363a0af8..68b84d40d 100644 --- a/website/docs/from_provider/family/raw.dart +++ b/website/docs/from_provider/family/raw.dart @@ -1,10 +1,27 @@ import 'dart:math'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +@immutable +abstract class Equatable { + const Equatable(); + + List get props; +} + /* SNIPPET START */ -typedef ParamsType = ({int seed, int max}); +class ParamsType extends Equatable { + const ParamsType({required this.seed, required this.max}); + + final int seed; + final int max; + + @override + List get props => [seed, max]; +} -final randomProvider = Provider.family.autoDispose((ref, params) { +final randomProvider = + Provider.family.autoDispose((ref, params) { return Random(params.seed).nextInt(params.max); }); diff --git a/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart b/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart index 3317e08d0..32fd0ac82 100644 --- a/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart +++ b/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart @@ -36,5 +36,4 @@ final evenItemsProvider = AutoDisposeProvider>.internal( ); typedef EvenItemsRef = AutoDisposeProviderRef>; -// ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart index c591a8b94..2cc21b77c 100644 --- a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart +++ b/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart @@ -37,5 +37,4 @@ final cachedDiceRollProvider = AutoDisposeProvider.internal( ); typedef CachedDiceRollRef = AutoDisposeProviderRef; -// ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/combine/combine.g.dart b/website/docs/from_provider/providers_limitations/combine/combine.g.dart index 79038d9ec..b2265e35c 100644 --- a/website/docs/from_provider/providers_limitations/combine/combine.g.dart +++ b/website/docs/from_provider/providers_limitations/combine/combine.g.dart @@ -36,5 +36,4 @@ final doubledProvider = AutoDisposeProvider.internal( ); typedef DoubledRef = AutoDisposeProviderRef; -// ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart index 8b60af905..228531aa1 100644 --- a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart +++ b/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart @@ -36,5 +36,4 @@ final evenItemsProvider = AutoDisposeProvider>.internal( ); typedef EvenItemsRef = AutoDisposeProviderRef>; -// ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member From a172779527176b03f3496973b2ad82701d774b8e Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Tue, 11 Jul 2023 12:05:01 +0200 Subject: [PATCH 25/38] Re-structured the articles --- website/docs/from_provider/introduction.mdx | 23 ------ .../async_values/async_values.dart | 0 .../async_values/async_values.g.dart | 0 .../async_values/index.tsx | 0 .../async_values/raw.dart | 0 .../auto_dispose/auto_dispose.dart | 0 .../auto_dispose/auto_dispose.g.dart | 0 .../auto_dispose/index.tsx | 0 .../auto_dispose/raw.dart | 0 .../combine/combine.dart | 0 .../combine/combine.g.dart | 0 .../combine/index.tsx | 0 .../combine/raw.dart | 0 .../motivation.mdx} | 81 ++++++++++++++++--- .../override/index.tsx | 0 .../override/override.dart | 0 .../override/raw.dart | 0 .../same_type/index.tsx | 0 .../same_type/raw.dart | 0 .../same_type/same_type.dart | 0 .../same_type/same_type.g.dart | 0 .../side_effects/index.tsx | 0 .../side_effects/raw.dart | 0 .../side_effects/side_effects.dart | 0 ...ilarities.mdx => provider_vs_riverpod.mdx} | 5 +- .../{migrating_tips.mdx => quickstart.mdx} | 80 +++++++++++++++--- .../docs/from_provider/towards_riverpod.mdx | 46 ----------- website/sidebars.js | 8 +- 28 files changed, 146 insertions(+), 97 deletions(-) delete mode 100644 website/docs/from_provider/introduction.mdx rename website/docs/from_provider/{providers_limitations => motivation}/async_values/async_values.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/async_values/async_values.g.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/async_values/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/async_values/raw.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/auto_dispose/auto_dispose.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/auto_dispose/auto_dispose.g.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/auto_dispose/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/auto_dispose/raw.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/combine/combine.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/combine/combine.g.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/combine/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/combine/raw.dart (100%) rename website/docs/from_provider/{providers_limitations/providers_limitations.mdx => motivation/motivation.mdx} (66%) rename website/docs/from_provider/{providers_limitations => motivation}/override/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/override/override.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/override/raw.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/same_type/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/same_type/raw.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/same_type/same_type.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/same_type/same_type.g.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/side_effects/index.tsx (100%) rename website/docs/from_provider/{providers_limitations => motivation}/side_effects/raw.dart (100%) rename website/docs/from_provider/{providers_limitations => motivation}/side_effects/side_effects.dart (100%) rename website/docs/from_provider/{differences_and_similarities.mdx => provider_vs_riverpod.mdx} (98%) rename website/docs/from_provider/{migrating_tips.mdx => quickstart.mdx} (54%) delete mode 100644 website/docs/from_provider/towards_riverpod.mdx diff --git a/website/docs/from_provider/introduction.mdx b/website/docs/from_provider/introduction.mdx deleted file mode 100644 index efa66d1da..000000000 --- a/website/docs/from_provider/introduction.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Introduction ---- - - -This section is designed for people familiar with the [Provider] package who -wants to learn about Riverpod. - -In particular, this section should answer the following: - - Since Provider is widely popular, why would one migrate to Riverpod? - - What concrete advantages do I get? - - How can I migrate towards Riverpod? - - Can I migrate incrementally? - - etc. - -By the end of this section you should be convinced that Riverpod is to be prefered over Provider. - -**Riverpod is indeed a more modern, recommended and reliable approach when compared to Provider**. - -Riverpod offers better State Management capabilities, better Caching strategies and a simplified Reactivty model. -Whereas, Provider is currently lacking in many areas with no way forward. - -[provider]: https://pub.dev/packages/provider diff --git a/website/docs/from_provider/providers_limitations/async_values/async_values.dart b/website/docs/from_provider/motivation/async_values/async_values.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/async_values/async_values.dart rename to website/docs/from_provider/motivation/async_values/async_values.dart diff --git a/website/docs/from_provider/providers_limitations/async_values/async_values.g.dart b/website/docs/from_provider/motivation/async_values/async_values.g.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/async_values/async_values.g.dart rename to website/docs/from_provider/motivation/async_values/async_values.g.dart diff --git a/website/docs/from_provider/providers_limitations/async_values/index.tsx b/website/docs/from_provider/motivation/async_values/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/async_values/index.tsx rename to website/docs/from_provider/motivation/async_values/index.tsx diff --git a/website/docs/from_provider/providers_limitations/async_values/raw.dart b/website/docs/from_provider/motivation/async_values/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/async_values/raw.dart rename to website/docs/from_provider/motivation/async_values/raw.dart diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart b/website/docs/from_provider/motivation/auto_dispose/auto_dispose.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.dart rename to website/docs/from_provider/motivation/auto_dispose/auto_dispose.dart diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart b/website/docs/from_provider/motivation/auto_dispose/auto_dispose.g.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/auto_dispose/auto_dispose.g.dart rename to website/docs/from_provider/motivation/auto_dispose/auto_dispose.g.dart diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/index.tsx b/website/docs/from_provider/motivation/auto_dispose/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/auto_dispose/index.tsx rename to website/docs/from_provider/motivation/auto_dispose/index.tsx diff --git a/website/docs/from_provider/providers_limitations/auto_dispose/raw.dart b/website/docs/from_provider/motivation/auto_dispose/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/auto_dispose/raw.dart rename to website/docs/from_provider/motivation/auto_dispose/raw.dart diff --git a/website/docs/from_provider/providers_limitations/combine/combine.dart b/website/docs/from_provider/motivation/combine/combine.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/combine/combine.dart rename to website/docs/from_provider/motivation/combine/combine.dart diff --git a/website/docs/from_provider/providers_limitations/combine/combine.g.dart b/website/docs/from_provider/motivation/combine/combine.g.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/combine/combine.g.dart rename to website/docs/from_provider/motivation/combine/combine.g.dart diff --git a/website/docs/from_provider/providers_limitations/combine/index.tsx b/website/docs/from_provider/motivation/combine/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/combine/index.tsx rename to website/docs/from_provider/motivation/combine/index.tsx diff --git a/website/docs/from_provider/providers_limitations/combine/raw.dart b/website/docs/from_provider/motivation/combine/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/combine/raw.dart rename to website/docs/from_provider/motivation/combine/raw.dart diff --git a/website/docs/from_provider/providers_limitations/providers_limitations.mdx b/website/docs/from_provider/motivation/motivation.mdx similarity index 66% rename from website/docs/from_provider/providers_limitations/providers_limitations.mdx rename to website/docs/from_provider/motivation/motivation.mdx index 260a4c942..b68a848a8 100644 --- a/website/docs/from_provider/providers_limitations/providers_limitations.mdx +++ b/website/docs/from_provider/motivation/motivation.mdx @@ -1,5 +1,5 @@ --- -title: Provider's limitations +title: Motivation --- import Tabs from "@theme/Tabs"; @@ -17,6 +17,23 @@ import { ConditionalSnippet, } from "../../../src/components/CodeSnippet"; +This in-depth article is meant to show why Riverpod is even a thing. + +In particular, this section should answer the following: + - Since Provider is widely popular, why would one migrate to Riverpod? + - What concrete advantages do I get? + - How can I migrate towards Riverpod? + - Can I migrate incrementally? + - etc. + +By the end of this section you should be convinced that Riverpod is to be prefered over Provider. + +**Riverpod is indeed a more modern, recommended and reliable approach when compared to Provider**. + +Riverpod offers better State Management capabilities, better Caching strategies and a simplified Reactivty model. +Whereas, Provider is currently lacking in many areas with no way forward. + +## Provider's Limitations Provider has fundamental issues due to being restricted by the InheritedWidget API. Inherently, Provider is a "simpler `InheritedWidget`"; @@ -24,7 +41,7 @@ Provider is merely an InheritedWidget wrapper, and thus it's limited by it. Here's a list of known Provider issues. -## Provider can't keep two (or more) providers of the same "type" +### Provider can't keep two (or more) providers of the same "type" Declaring two `Provider` will result into unreliable behavior: `InheritedWidget`'s API will obtain only *one of the two*: the closest `Provider` ancestor. While a [workaround] is explained in Provider's @@ -35,7 +52,7 @@ By removing this limitation, we can freely split logic into tiny pieces, like so -## Providers reasonably emit only one value at a time +### Providers reasonably emit only one value at a time When reading an external RESTful API, it's quite common to show the last read value, while a new call loads the next one. Riverpod allows this behavior via emitting two values at a time (i.e. a previous data value, @@ -52,7 +69,7 @@ In the previous snippet, watching `evenItemsProvider` will produce the following With Provider, the above features aren't remotely achievable, and even less easy to workaround. -## Combining providers is hard and error prone +### Combining providers is hard and error prone With Provider we may be tempted to use `context.watch` inside provider's `create`. This would be unreliable, as `didChangeDependencies` may be triggered even if no dependency has changed (e.g. such as when there's a GlobalKey involved in the widget tree). @@ -67,13 +84,13 @@ with simple yet powerful utilites such as [ref.watch] and [ref.listen]: Combining values feels natural with Riverpod: dependencies are readable and the APIs remain the same. -## Lack of safety +### Lack of safety With Provider, it's common to end-up with a `ProviderNotFoundException` during refactors and / or during large changes. Indeed, this runtime exception *was* one of the main reasons Riverpod was created in the first place. Although it brings much more utility than this, Riverpod simply can't throw this exception. -## Disposing of state is difficult +### Disposing of state is difficult `InheritedWidget` [can't react when a consumer stops listening to them]. This prevents the ability for Provider to automatically destroy its providers' state when they're no-longer used. @@ -88,7 +105,7 @@ These two APIs enable flexible and creative caching strategies (e.g. time-based Unluckily, there's no way to implement this with a raw `InheritedWidget`, and thus with Provider. -## Lack of a reliable parametrization mechanism +### Lack of a reliable parametrization mechanism Riverpod allows its user to declare "parametrized" Providers with the [.family modifier]. Indeed, `.family` is one of Riverpod's most powerful feature and it is core to its innovations, e.g. it enables enormous [simplification of logic]. @@ -104,7 +121,7 @@ This implies significant memory leaks if some provider state is "dynamically mou to a build a Provider, which is exactly what `.family` does. Thus, obtaining a `.family` equivalent for Provider is fundamentally impossible at the moment in time. -## Testing is tedious +### Testing is tedious To be able to write a test, you *have to* re-define providers inside each test. With Riverpod, providers are ready to use inside tests, by default. Furthermore, Riverpod exposes a @@ -117,7 +134,7 @@ Testing the combined state snippet above would be as simple as the following: For more info about testing, see [Testing]. -## Triggering side effects isn't straightforward +### Triggering side effects isn't straightforward Since `InheritedWidget` has no `onChange` callback, Provider can't have one. This is problematic for navigation, such as for snackbars, modals, etc. @@ -125,6 +142,52 @@ Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutte +## Towards Riverpod + + +Originally, a major version of Provider was planned to ship, as a way to solve +the aforementioned problems. +But it was then decided against it, as this would have been +"too breaking" and even controversial, because of the new `ConsumerWidget` API. +Since Provider is still one of the most used Flutter packages, it was instead decided +to create a separate package, and thus Riverpod was created. + +Creating a separate package enabled: + - Ease of migration for whoever wants to, by also enabling the temporary use of both approaches, *at the same time*; + - Allow folks to stick to Provider if they dislike Riverpod in principle, or if they didn't find it reliable yet; + - Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. + +Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). + +### The relationship between Riverpod and Provider +Still, conceptually, Riverpod and Provider are fairly similar. +Both packages fill a similar role. Both try to: + +- cache and dispose some stateful objects; +- offer a way to mock those objects during tests; +- offer a way for Widgets to listen to those objects in a simple way. + +You can think of Riverpod as what Provider could've been if it continued to mature for a few years. + +### The breaking change +The only true downside of Riverpod is that it requires changing the widget type to work: + +- Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. +- Instead of extending `StatefulWidget`, with Riverpod you should extend `ConsumerStatefulWidget`. + +But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. + +### Choosing the right library +You're probably asking yourself: +*"So, as a Provider user, should I use Provider or Riverpod?"*. + +We want to answer to this question very clearly: + + You probably should be using Riverpod + +Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. + + [ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider [ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/docs/from_provider/providers_limitations/override/index.tsx b/website/docs/from_provider/motivation/override/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/override/index.tsx rename to website/docs/from_provider/motivation/override/index.tsx diff --git a/website/docs/from_provider/providers_limitations/override/override.dart b/website/docs/from_provider/motivation/override/override.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/override/override.dart rename to website/docs/from_provider/motivation/override/override.dart diff --git a/website/docs/from_provider/providers_limitations/override/raw.dart b/website/docs/from_provider/motivation/override/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/override/raw.dart rename to website/docs/from_provider/motivation/override/raw.dart diff --git a/website/docs/from_provider/providers_limitations/same_type/index.tsx b/website/docs/from_provider/motivation/same_type/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/same_type/index.tsx rename to website/docs/from_provider/motivation/same_type/index.tsx diff --git a/website/docs/from_provider/providers_limitations/same_type/raw.dart b/website/docs/from_provider/motivation/same_type/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/same_type/raw.dart rename to website/docs/from_provider/motivation/same_type/raw.dart diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.dart b/website/docs/from_provider/motivation/same_type/same_type.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/same_type/same_type.dart rename to website/docs/from_provider/motivation/same_type/same_type.dart diff --git a/website/docs/from_provider/providers_limitations/same_type/same_type.g.dart b/website/docs/from_provider/motivation/same_type/same_type.g.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/same_type/same_type.g.dart rename to website/docs/from_provider/motivation/same_type/same_type.g.dart diff --git a/website/docs/from_provider/providers_limitations/side_effects/index.tsx b/website/docs/from_provider/motivation/side_effects/index.tsx similarity index 100% rename from website/docs/from_provider/providers_limitations/side_effects/index.tsx rename to website/docs/from_provider/motivation/side_effects/index.tsx diff --git a/website/docs/from_provider/providers_limitations/side_effects/raw.dart b/website/docs/from_provider/motivation/side_effects/raw.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/side_effects/raw.dart rename to website/docs/from_provider/motivation/side_effects/raw.dart diff --git a/website/docs/from_provider/providers_limitations/side_effects/side_effects.dart b/website/docs/from_provider/motivation/side_effects/side_effects.dart similarity index 100% rename from website/docs/from_provider/providers_limitations/side_effects/side_effects.dart rename to website/docs/from_provider/motivation/side_effects/side_effects.dart diff --git a/website/docs/from_provider/differences_and_similarities.mdx b/website/docs/from_provider/provider_vs_riverpod.mdx similarity index 98% rename from website/docs/from_provider/differences_and_similarities.mdx rename to website/docs/from_provider/provider_vs_riverpod.mdx index 983adfd43..a2184547e 100644 --- a/website/docs/from_provider/differences_and_similarities.mdx +++ b/website/docs/from_provider/provider_vs_riverpod.mdx @@ -1,5 +1,5 @@ --- -title: Differences and Similarities between Provider and Riverpod +title: Provider vs Riverpod --- import Tabs from "@theme/Tabs"; @@ -13,8 +13,7 @@ import { } from "../../src/components/CodeSnippet"; -This recap should suffice when performing migrations, as it quickly summarizes -defferences and similarities between Provider and Riverpod. +This article recaps the defferences and the similarities between Provider and Riverpod. ## Defining providers diff --git a/website/docs/from_provider/migrating_tips.mdx b/website/docs/from_provider/quickstart.mdx similarity index 54% rename from website/docs/from_provider/migrating_tips.mdx rename to website/docs/from_provider/quickstart.mdx index 776abda30..fdf6dfab4 100644 --- a/website/docs/from_provider/migrating_tips.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -1,25 +1,56 @@ --- -title: Migrating tips +title: Quickstart --- +## Try Riverpod online + +Before anything, we'd suggest you try out Riverpod in a sandbox to test its features out. + +The following example should be familiar and readable enough: + - dartpad.dev example (link) + - marvel app example, or another github repo example (link) + - ... more? + +## Migrating + +This section is designed for people familiar with the [Provider] package who +wants to learn about Riverpod. Migrating from Provider to Riverpod can be very straightforward. Migrating basically consists in a few steps that can be done in an *incremental* way. -## A gentle start +### Start gently *If you have an existing app, don't try to migrate all your providers at once.* Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. - This will requite a bit of a paradigm shift, so it may be difficult to do initially. -Take your time, as it is important to get yourself familiar with Riverpod first. + +Take your time, as it is important to get yourself familiar with Riverpod first. +The following is perfectly fine to start with: + +```dart +// If you have this... +class MyNotifier extends ChangeNotifier { + int state = 0; + + void increment() { + state++; + notifyListeners(); + } +} + +// ... just add this! +final myNotifierProvider = ChangeNotifierProvider((ref) { + return MyNotifier(); +}); +``` It's fine to keep using `ChangeNotifier` using Riverpod and not use the latest fancy Riverpod features ASAP. While you should strive toward moving all your application to Riverpod in the long-run, **don't burn yourself out**. -Do it one provider at a time. +Do it one provider at a time. -## Riverpod and Provider can coexist +### Riverpod and Provider can coexist *Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* Indeed, using import aliases, it is possible to use the two APIs altogheter. @@ -31,18 +62,31 @@ If you plan on doing this, consider using import aliases for each Provider impor A full guide onto how to effectively implement import aliases is incoming soon. ::: -## Migrating one Provider at a time +### Migrate one Provider at a time *All Providers from pkg:provider have a strict equivalent in Riverpod*. In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but it's a gentle and very easy way to start your migration. -Thus, migrating consists in moving a `provider.Provider` definition *outside* of `MultiProvider`, -and change it to a `riverpod.Provider` definition (see the differences below). +Take the above example. Fully migrating that Provider means writing the following: +```dart +class MyNotifier extends Notifier { + @override + int build() => 0; -## Code Generation + void increment() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); +``` + +Thus, migrating consists in moving `provider.Provider` definitions *outside* of `MultiProvider`, +defining it as a final global `riverpod.Provider` variable and using `riverpod`'s new classes where needed. + + +### Code Generation [Code generation] is recommended to use Riverpod the *cutting edge* way. Even so, don't try using codegen right away. @@ -51,7 +95,21 @@ Instead, this should be taken as a further step forward. Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. +With codegen, the above example becomes: +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; + + void increment() => state++; +} +``` + + +[provider]: https://pub.dev/packages/provider [ChangeNotifierProvider]: /docs/providers/change_notifier_provider [Code generation]: /docs/about_code_generation -[AsyncNotifiers]: /docs/providers/notifier_provider \ No newline at end of file +[AsyncNotifiers]: /docs/providers/notifier_provider + diff --git a/website/docs/from_provider/towards_riverpod.mdx b/website/docs/from_provider/towards_riverpod.mdx deleted file mode 100644 index 2201878e5..000000000 --- a/website/docs/from_provider/towards_riverpod.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Towards Riverpod ---- - - -Originally, a major version of Provider was planned to ship, as a way to solve -the aforementioned problems. -But it was then decided against it, as this would have been -"too breaking" and even controversial, because of the new `ConsumerWidget` API. -Since Provider is still one of the most used Flutter packages, it was instead decided -to create a separate package, and thus Riverpod was created. - -Creating a separate package enabled: - - Ease of migration for whoever wants to, by also enabling the temporary use of both approaches, *at the same time*; - - Allow folks to stick to Provider if they dislike Riverpod in principle, or if they didn't find it reliable yet; - - Experimentation, allowing for Riverpod to search for production-ready solutions to the various Provider's technical limitations. - -Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). - -## The relationship between Riverpod and Provider -Still, conceptually, Riverpod and Provider are fairly similar. -Both packages fill a similar role. Both try to: - -- cache and dispose some stateful objects; -- offer a way to mock those objects during tests; -- offer a way for Widgets to listen to those objects in a simple way. - -You can think of Riverpod as what Provider could've been if it continued to mature for a few years. - -## The breaking change -The only true downside of Riverpod is that it requires changing the widget type to work: - -- Instead of extending `StatelessWidget`, with Riverpod you should extend `ConsumerWidget`. -- Instead of extending `StatefulWidget`, with Riverpod you should extend `ConsumerStatefulWidget`. - -But this inconvenience is fairly minor in the grand scheme of things. And this requirement might, one day, disappear. - -## Choosing the right library -You're probably asking yourself: -*"So, as a Provider user, should I use Provider or Riverpod?"*. - -We want to answer to this question very clearly: - - You probably should be using Riverpod - -Riverpod is overhaul better designed and could lead to drastic simplifications of your logic. diff --git a/website/sidebars.js b/website/sidebars.js index 17d9b5c8d..2dc0a80d8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -6,11 +6,9 @@ module.exports = { type: "category", label: "Riverpod for Provider Users", items: [ - "from_provider/introduction", - "from_provider/providers_limitations/providers_limitations", - "from_provider/towards_riverpod", - "from_provider/migrating_tips", - "from_provider/differences_and_similarities", + "from_provider/quickstart", + "from_provider/provider_vs_riverpod", + "from_provider/motivation/motivation", ], }, "about_code_generation", From d817125280ecb9973234e4b49befc12c4bfa3404 Mon Sep 17 00:00:00 2001 From: Luca Venir Date: Thu, 13 Jul 2023 23:20:55 +0200 Subject: [PATCH 26/38] Start with `ChangeNotifierProvider` Co-authored-by: Remi Rousselet --- website/docs/from_provider/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index fdf6dfab4..043c90fc3 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -19,7 +19,7 @@ wants to learn about Riverpod. Migrating from Provider to Riverpod can be very straightforward. Migrating basically consists in a few steps that can be done in an *incremental* way. -### Start gently +### Start with ChangeNotifierProvider *If you have an existing app, don't try to migrate all your providers at once.* Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. This will requite a bit of a paradigm shift, so it may be difficult to do initially. From bd2acd15fd834b5a30ba93a45c142affed7eb6ad Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 13 Jul 2023 23:24:15 +0200 Subject: [PATCH 27/38] Quick hotfixes --- website/docs/from_provider/quickstart.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 043c90fc3..423bacf65 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -19,7 +19,7 @@ wants to learn about Riverpod. Migrating from Provider to Riverpod can be very straightforward. Migrating basically consists in a few steps that can be done in an *incremental* way. -### Start with ChangeNotifierProvider +### Start with `ChangeNotifierProvider` *If you have an existing app, don't try to migrate all your providers at once.* Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. This will requite a bit of a paradigm shift, so it may be difficult to do initially. @@ -63,7 +63,7 @@ A full guide onto how to effectively implement import aliases is incoming soon. ::: ### Migrate one Provider at a time -*All Providers from pkg:provider have a strict equivalent in Riverpod*. +*Almost* all Providers from pkg:provider have a strict equivalent in pkg:riverpod. In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but From 4d6ea01f94d39b57147bcb127b1d2e75a78bb064 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 13 Jul 2023 23:41:28 +0200 Subject: [PATCH 28/38] ProxyProvider migration --- website/docs/from_provider/quickstart.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 423bacf65..53e4dc7d6 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -85,6 +85,20 @@ final myNotifierProvider = NotifierProvider(MyNotifier.new); Thus, migrating consists in moving `provider.Provider` definitions *outside* of `MultiProvider`, defining it as a final global `riverpod.Provider` variable and using `riverpod`'s new classes where needed. +### Migrating a `ProxyProvider` +Within pkg:Provider, `ProxyProvider` is used to combine values from other Providers; +its build depends on the value of other providers, reactively. + +With Riverpod, instead, Providers [are composable by default]; therefore, when migrating a `ProxyProvider` +you'll simply need to write `ref.watch` if you want a direct dependency from a Provider to another, or `ref.listen` +if your Proxy used to simply trigger a side-effect. + +If anything, combining values with Riverpod should feel simpler and straightforward; thus, the migration should greatly +simplify your code. + +Furthermore, there are no shanenigans about combining more than two providers together: +just add another `ref.watch` (or `ref.listen`) and you'll be good to go. + ### Code Generation [Code generation] is recommended to use Riverpod the *cutting edge* way. @@ -112,4 +126,5 @@ class MyNotifier extends _$MyNotifier { [ChangeNotifierProvider]: /docs/providers/change_notifier_provider [Code generation]: /docs/about_code_generation [AsyncNotifiers]: /docs/providers/notifier_provider +[are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone From afa29e2c9a47c1927a7abfcd63e5928048636469 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 13 Jul 2023 23:45:58 +0200 Subject: [PATCH 29/38] Moved the "cohexist" section down --- website/docs/from_provider/quickstart.mdx | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 53e4dc7d6..ae431c69b 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -50,18 +50,6 @@ While you should strive toward moving all your application to Riverpod in the lo **don't burn yourself out**. Do it one provider at a time. -### Riverpod and Provider can coexist -*Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* - -Indeed, using import aliases, it is possible to use the two APIs altogheter. -This is also great for readibilty and it removes any ambiguous API usage. - -If you plan on doing this, consider using import aliases for each Provider import in your codebase. - -:::info -A full guide onto how to effectively implement import aliases is incoming soon. -::: - ### Migrate one Provider at a time *Almost* all Providers from pkg:provider have a strict equivalent in pkg:riverpod. @@ -85,6 +73,19 @@ final myNotifierProvider = NotifierProvider(MyNotifier.new); Thus, migrating consists in moving `provider.Provider` definitions *outside* of `MultiProvider`, defining it as a final global `riverpod.Provider` variable and using `riverpod`'s new classes where needed. + +### Riverpod and Provider can coexist +*Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* + +Indeed, using import aliases, it is possible to use the two APIs altogheter. +This is also great for readibilty and it removes any ambiguous API usage. + +If you plan on doing this, consider using import aliases for each Provider import in your codebase. + +:::info +A full guide onto how to effectively implement import aliases is incoming soon. +::: + ### Migrating a `ProxyProvider` Within pkg:Provider, `ProxyProvider` is used to combine values from other Providers; its build depends on the value of other providers, reactively. From 6b4c4002c318e5659e53fb330929c371181fa3b4 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Thu, 13 Jul 2023 23:54:36 +0200 Subject: [PATCH 30/38] Start with the leaves section --- website/docs/from_provider/quickstart.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index ae431c69b..6892bd37f 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -73,6 +73,15 @@ final myNotifierProvider = NotifierProvider(MyNotifier.new); Thus, migrating consists in moving `provider.Provider` definitions *outside* of `MultiProvider`, defining it as a final global `riverpod.Provider` variable and using `riverpod`'s new classes where needed. +### Starts with *leaves* + +Start with Providers that do not depend on anything else, i.e. start with the *leaves* in your dependency tree. +Once you have migrated all of the leaves, you can then move on to the providers that depend on leaves. + +In other words, avoid migrating `ProxyProvider`s at first; tackle them once all of their dependencies have been migrated. + +This should boost and simplify the migration process, while also minimizing / tracking down any errors. + ### Riverpod and Provider can coexist *Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* From ed9d746c2d46df21993e2f31d70e4c5eafa663fc Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 00:27:05 +0200 Subject: [PATCH 31/38] No need to use Consumer right away --- website/docs/from_provider/quickstart.mdx | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 6892bd37f..6cdfd562b 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -95,6 +95,35 @@ If you plan on doing this, consider using import aliases for each Provider impor A full guide onto how to effectively implement import aliases is incoming soon. ::: + +### You don't *have to* use `Consumer` right away + +It's important to keep in mind that there is no need to *immediately* use [Riverpod's `Consumer` APIs]. +If you've just started the migration, [as mentioned above], you should probably start with `ChangeNotifierProvider`. + + +Say you've just migrated `myChangeNotifierProvider` like so: +```dart +class MyChangeNotifier extends ChangeNotifier {} + +final myChangeNotifierProvider = ChangeNotifierProvider((ref) { + return MyChangeNotifier(); +}); +``` + +If your inner code is depending on pkg:Provider consume APIs, the following is more than enough to start with your new pkg:Riverpod's providers. +```dart +MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: ref.watch(myNotifier.notifier)), + ] +) +``` + +This way, only the root Widget has to be initially converted into a `ConsumerWidget`. +This should ease the migration towards pkg:Riverpod even more. + + ### Migrating a `ProxyProvider` Within pkg:Provider, `ProxyProvider` is used to combine values from other Providers; its build depends on the value of other providers, reactively. @@ -137,4 +166,5 @@ class MyNotifier extends _$MyNotifier { [Code generation]: /docs/about_code_generation [AsyncNotifiers]: /docs/providers/notifier_provider [are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone - +[as mentioned above]: /docs/from_provider/quickstart#start-with-changenotifierprovider +[Riverpod's `Consumer` APIs]: /docs/concepts/reading \ No newline at end of file From d804a151f354581d6c80e6d14011c65bcb6919fd Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 00:27:14 +0200 Subject: [PATCH 32/38] About eager initialization --- website/docs/from_provider/quickstart.mdx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 6cdfd562b..e6c81f8a8 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -160,6 +160,16 @@ class MyNotifier extends _$MyNotifier { } ``` +### Eager initialization + +Since Riverpod's providers are final global variables, they are [lazy by default]. + +If you need to initialize some warm-up data or a useful service on startup, +the best way to do it is to first read your provider in the place where you used to put `MultiProvider`. + +In other words, since Riverpod can't be forced to be eager initialized, they can be read and cached +in your startup phase, so that they're warm and ready when needed inside the rest of your application. + [provider]: https://pub.dev/packages/provider [ChangeNotifierProvider]: /docs/providers/change_notifier_provider @@ -167,4 +177,5 @@ class MyNotifier extends _$MyNotifier { [AsyncNotifiers]: /docs/providers/notifier_provider [are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone [as mentioned above]: /docs/from_provider/quickstart#start-with-changenotifierprovider -[Riverpod's `Consumer` APIs]: /docs/concepts/reading \ No newline at end of file +[Riverpod's `Consumer` APIs]: /docs/concepts/reading +[lazy by default]: /docs/concepts/provider_lifecycles \ No newline at end of file From 66571e05340f12216a3b4afdc78693309a12d5cf Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 00:39:27 +0200 Subject: [PATCH 33/38] There's no `ConsumerN` --- .../from_provider/provider_vs_riverpod.mdx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/website/docs/from_provider/provider_vs_riverpod.mdx b/website/docs/from_provider/provider_vs_riverpod.mdx index a2184547e..0b44bbd98 100644 --- a/website/docs/from_provider/provider_vs_riverpod.mdx +++ b/website/docs/from_provider/provider_vs_riverpod.mdx @@ -137,7 +137,7 @@ Provider optionally comes with a widget named `Consumer` (and variants such as ` for reading providers. `Consumer` is helpful as a performance optimization, by allowing more granular rebuilds -of the widget tree – updating only the revelant widgets when the state changes: +of the widget tree - updating only the revelant widgets when the state changes: As such, if a provider was defined as: @@ -178,6 +178,26 @@ Consumer( Notice how `Consumer` gives us a `WidgetRef` object. This is the same object as we saw in the previous part related to `ConsumerWidget`. +### There is no `ConsumerN` equivalent in Riverpod + +Notice how pkg:Provider's `Consumer2`, `Consumer3` and such aren't needed nor missed in Riverpod. + +With Riverpod, if you want to read values from multiple providers, you can simply write multiple `ref.watch` statements, +like so: + +```dart +Consumer( + builder: (context, ref, child) { + Model1 model = ref.watch(model1Provider); + Model2 model = ref.watch(model2Provider); + Model3 model = ref.watch(model3Provider); + // ... + } +) +``` + +When compared to pkg:Provider's `ConsumerN` APIs, the above solution feels way less heavy and it should be easier to understand. + ## Combining providers: ProxyProvider with stateless objects When using Provider, the official way of combining providers is using the From ff21c06c945959b4fe5942f53dd9dcd0487775f0 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 00:46:04 +0200 Subject: [PATCH 34/38] watch.select VS ref.watch(provider.select) --- website/docs/from_provider/provider_vs_riverpod.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/from_provider/provider_vs_riverpod.mdx b/website/docs/from_provider/provider_vs_riverpod.mdx index 0b44bbd98..61375333b 100644 --- a/website/docs/from_provider/provider_vs_riverpod.mdx +++ b/website/docs/from_provider/provider_vs_riverpod.mdx @@ -125,10 +125,11 @@ Riverpod uses the same terminology as Provider for reading providers. - `BuildContext.watch` -> `WidgetRef.watch` - `BuildContext.read` -> `WidgetRef.read` +- `BuildContext.select` -> `WidgetRef.watch(myProvider.select)` The rules for `context.watch` vs `context.read` applies to Riverpod too: Inside the `build` method, use "watch". Inside click handlers and other events, -use "read". +use "read". When in need of filtering out values and rebuilds, use "select". ::: ## Reading providers: Consumer From 3d97a9d6701f8643fe37d8715dbd262323a041df Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 01:23:35 +0200 Subject: [PATCH 35/38] Re-arranged the quickstart again --- website/docs/from_provider/quickstart.mdx | 126 +++++++++++----------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index e6c81f8a8..940390321 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -2,30 +2,23 @@ title: Quickstart --- -## Try Riverpod online - -Before anything, we'd suggest you try out Riverpod in a sandbox to test its features out. - -The following example should be familiar and readable enough: - - dartpad.dev example (link) - - marvel app example, or another github repo example (link) - - ... more? - -## Migrating - This section is designed for people familiar with the [Provider] package who wants to learn about Riverpod. -Migrating from Provider to Riverpod can be very straightforward. +Before anything, we suggest you read the short [getting started] article and try out the small +[sandbox] example to test Riverpod's features out. If you like what you see there, you should then +definitively consider a migration. + +Indeed, migrating from Provider to Riverpod can be very straightforward. + Migrating basically consists in a few steps that can be done in an *incremental* way. -### Start with `ChangeNotifierProvider` -*If you have an existing app, don't try to migrate all your providers at once.* -Don't immediately try to change your `ChangeNotifiers` into the more modern [AsyncNotifiers] from Riverpod. -This will requite a bit of a paradigm shift, so it may be difficult to do initially. +## Start with `ChangeNotifierProvider` -Take your time, as it is important to get yourself familiar with Riverpod first. -The following is perfectly fine to start with: +It's fine to keep using `ChangeNotifier` while transitioning towards Riverpod, +and not use its latest fancy features ASAP. + +Indeed, the following is perfectly fine to start with: ```dart // If you have this... @@ -44,36 +37,21 @@ final myNotifierProvider = ChangeNotifierProvider((ref) { }); ``` -It's fine to keep using `ChangeNotifier` using Riverpod and not use the latest fancy Riverpod features ASAP. +As you can see Riverpod exposes a [ChangeNotifierProvider] class, +which is there precisely to support migrations from pkg:Provider. -While you should strive toward moving all your application to Riverpod in the long-run, -**don't burn yourself out**. -Do it one provider at a time. - -### Migrate one Provider at a time -*Almost* all Providers from pkg:provider have a strict equivalent in pkg:riverpod. +Keep in mind that this provider is not recommended when writing new code, +and it is not the best way to use Riverpod, but it's a gentle and very easy way to start your migration. -In particular, you'll find [ChangeNotifierProvider], which is there to enable migration from Provider. -This provider is not recommended when writing new code, and it is not the best way to use Riverpod, but -it's a gentle and very easy way to start your migration. +:::tip +There is no rush to *immediately* try to change your `ChangeNotifier`s into the more modern [Riverpod's providers]. +Some requite a bit of a paradigm shift, so it may be difficult to do initially. -Take the above example. Fully migrating that Provider means writing the following: - -```dart -class MyNotifier extends Notifier { - @override - int build() => 0; - - void increment() => state++; -} - -final myNotifierProvider = NotifierProvider(MyNotifier.new); -``` - -Thus, migrating consists in moving `provider.Provider` definitions *outside* of `MultiProvider`, -defining it as a final global `riverpod.Provider` variable and using `riverpod`'s new classes where needed. +Take your time, as it is important to get yourself familiar with Riverpod first; +you'll quickly find out that *almost* all Providers from pkg:provider have a strict equivalent in pkg:riverpod. +::: -### Starts with *leaves* +## Starts with *leaves* Start with Providers that do not depend on anything else, i.e. start with the *leaves* in your dependency tree. Once you have migrated all of the leaves, you can then move on to the providers that depend on leaves. @@ -83,7 +61,7 @@ In other words, avoid migrating `ProxyProvider`s at first; tackle them once all This should boost and simplify the migration process, while also minimizing / tracking down any errors. -### Riverpod and Provider can coexist +## Riverpod and Provider can coexist *Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* Indeed, using import aliases, it is possible to use the two APIs altogheter. @@ -96,26 +74,18 @@ A full guide onto how to effectively implement import aliases is incoming soon. ::: -### You don't *have to* use `Consumer` right away +## You don't *have to* use `Consumer` right away It's important to keep in mind that there is no need to *immediately* use [Riverpod's `Consumer` APIs]. If you've just started the migration, [as mentioned above], you should probably start with `ChangeNotifierProvider`. +Consider `myNotifierProvider`, defined above. -Say you've just migrated `myChangeNotifierProvider` like so: -```dart -class MyChangeNotifier extends ChangeNotifier {} - -final myChangeNotifierProvider = ChangeNotifierProvider((ref) { - return MyChangeNotifier(); -}); -``` - -If your inner code is depending on pkg:Provider consume APIs, the following is more than enough to start with your new pkg:Riverpod's providers. +Since your inner code is probably depending on pkg:Provider's APIs, use the following to start consuming `ChangeNotifier`s with pkg:Riverpod. ```dart MultiProvider( providers: [ - ChangeNotifierProvider.value(value: ref.watch(myNotifier.notifier)), + ChangeNotifierProvider.value(value: ref.watch(myNotifierProvider.notifier)), ] ) ``` @@ -124,22 +94,47 @@ This way, only the root Widget has to be initially converted into a `ConsumerWid This should ease the migration towards pkg:Riverpod even more. -### Migrating a `ProxyProvider` + +## Migrate one Provider at a time + +If you have an existing app, don't try to migrate all your providers at once! + +While you should strive toward moving all your application to Riverpod in the long-run, +**don't burn yourself out**. +Do it one provider at a time. + +Take the above example. **Fully** migrating that `myNotifierProvider` to Riverpod means writing the following: + +```dart +class MyNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); +``` + +.. and it's _also_ needed to change how that provider is consumed, i.e. writing `ref.watch` in the place of each `context.watch` for this provider. + +This operation might take some time and might lead to some errors, so don't rush doing this all at once. + +## Migrating `ProxyProvider`s Within pkg:Provider, `ProxyProvider` is used to combine values from other Providers; its build depends on the value of other providers, reactively. With Riverpod, instead, Providers [are composable by default]; therefore, when migrating a `ProxyProvider` -you'll simply need to write `ref.watch` if you want a direct dependency from a Provider to another, or `ref.listen` -if your Proxy used to simply trigger a side-effect. +you'll simply need to write `ref.watch` if you want to declare a direct dependency from a Provider to another. If anything, combining values with Riverpod should feel simpler and straightforward; thus, the migration should greatly simplify your code. Furthermore, there are no shanenigans about combining more than two providers together: -just add another `ref.watch` (or `ref.listen`) and you'll be good to go. +just add another `ref.watch` and you'll be good to go. -### Code Generation +## Code Generation [Code generation] is recommended to use Riverpod the *cutting edge* way. Even so, don't try using codegen right away. @@ -160,7 +155,7 @@ class MyNotifier extends _$MyNotifier { } ``` -### Eager initialization +## Eager initialization Since Riverpod's providers are final global variables, they are [lazy by default]. @@ -170,11 +165,12 @@ the best way to do it is to first read your provider in the place where you used In other words, since Riverpod can't be forced to be eager initialized, they can be read and cached in your startup phase, so that they're warm and ready when needed inside the rest of your application. - +[getting started]: /docs/getting_started +[sandbox]: https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2 [provider]: https://pub.dev/packages/provider [ChangeNotifierProvider]: /docs/providers/change_notifier_provider [Code generation]: /docs/about_code_generation -[AsyncNotifiers]: /docs/providers/notifier_provider +[Riverpod's providers]: /docs/providers/notifier_provider [are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone [as mentioned above]: /docs/from_provider/quickstart#start-with-changenotifierprovider [Riverpod's `Consumer` APIs]: /docs/concepts/reading From f3a9b8b9624ee2c2c39a7804b89a29e0c0a0d074 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Fri, 14 Jul 2023 01:26:18 +0200 Subject: [PATCH 36/38] Hotfix --- website/docs/from_provider/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 940390321..95ca78664 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -5,7 +5,7 @@ title: Quickstart This section is designed for people familiar with the [Provider] package who wants to learn about Riverpod. -Before anything, we suggest you read the short [getting started] article and try out the small +Before anything, read the short [getting started] article and try out the small [sandbox] example to test Riverpod's features out. If you like what you see there, you should then definitively consider a migration. From c984c29c8575e1ebf875fcf4ac851aa2568498bb Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Mon, 17 Jul 2023 10:33:44 +0200 Subject: [PATCH 37/38] Added the codegen guide for ChangeNotifierProviders --- website/docs/from_provider/quickstart.mdx | 56 +++++++++++++++++------ 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 95ca78664..30335d5ee 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -133,17 +133,47 @@ simplify your code. Furthermore, there are no shanenigans about combining more than two providers together: just add another `ref.watch` and you'll be good to go. +## Eager initialization + +Since Riverpod's providers are final global variables, they are [lazy by default]. + +If you need to initialize some warm-up data or a useful service on startup, +the best way to do it is to first read your provider in the place where you used to put `MultiProvider`. + +In other words, since Riverpod can't be forced to be eager initialized, they can be read and cached +in your startup phase, so that they're warm and ready when needed inside the rest of your application. + +A full guide about eager initialization of pkg:Riverpod's providers [is available here]. ## Code Generation -[Code generation] is recommended to use Riverpod the *cutting edge* way. -Even so, don't try using codegen right away. +[Code generation] is recommended to use Riverpod the *future-proof* way. +As a side note, chances are that when metaprogramming will be a thing, codegen will be default for Riverpod. -Instead, this should be taken as a further step forward. +Unluckily, `@riverpod` can't generate code for `ChangeNotifierProvider`. +To overcome this, you can use the following utility extesion method: +```dart +extension ChangeNotifierWithCodeGenExtension on Ref { + T listenAndDisposeChangeNotifier(T notifier) { + notifier.addListener(notifyListeners); + onDispose(() => notifier.removeListener(notifyListeners)); + onDispose(notifier.dispose); + return notifier; + } +} +``` -Once the "base" migration is done, and you're positive that there are no more `ChangeNotifierProvider`s -in your codebase, you can start migrating towards the codegen apprach, which has a simpler yet different syntax. +And then, you can expose your `ChangeNotifier` with the following codegen syntax: +```dart +// ignore_for_file: unsupported_provider_value +@riverpod +MyNotifier example(ExampleRef ref) { + return ref.listenAndDisposeChangeNotifier(MyNotifier()); +} +``` -With codegen, the above example becomes: +Once the "base" migration is done, you can change your `ChangeNotifier` to `Notifier`, +thus eliminating the need for temporary extensions. +Taking up the previous examples, a "fully migrated" `Notifier` becomes: ```dart @riverpod @@ -155,15 +185,15 @@ class MyNotifier extends _$MyNotifier { } ``` -## Eager initialization - -Since Riverpod's providers are final global variables, they are [lazy by default]. +Once this is done, and you're positive that there are no more `ChangeNotifierProvider`s +in your codebase, you can get rid of the temporary extension definitively. -If you need to initialize some warm-up data or a useful service on startup, -the best way to do it is to first read your provider in the place where you used to put `MultiProvider`. +Keep in mind that, while being recommended, codegen is not *mandatory*. +It's good to reason about migrations incrementally: +if you feel like that implementing this migration *while* transitioning to +the code generation syntax in one single take might be too much, *that's fine*. -In other words, since Riverpod can't be forced to be eager initialized, they can be read and cached -in your startup phase, so that they're warm and ready when needed inside the rest of your application. +Following this guide, you *can* migrate towards codegen as a further step forward, later on. [getting started]: /docs/getting_started [sandbox]: https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2 From c61034cfc1d3251b97fc2c70ec8c3186824b9454 Mon Sep 17 00:00:00 2001 From: "venir.dev" Date: Mon, 17 Jul 2023 14:28:14 +0200 Subject: [PATCH 38/38] Changed the reading flow of the motivation page --- .../from_provider/motivation/motivation.mdx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/website/docs/from_provider/motivation/motivation.mdx b/website/docs/from_provider/motivation/motivation.mdx index b68a848a8..bd895a760 100644 --- a/website/docs/from_provider/motivation/motivation.mdx +++ b/website/docs/from_provider/motivation/motivation.mdx @@ -144,7 +144,16 @@ Instead, Riverpod simply offers `ref.listen`, which [integrates well with Flutte ## Towards Riverpod +Conceptually, Riverpod and Provider are fairly similar. +Both packages fill a similar role. Both try to: + +- cache and dispose some stateful objects; +- offer a way to mock those objects during tests; +- offer a way for Widgets to listen to those objects in a simple way. + +You can think of Riverpod as what Provider could've been if it continued to mature for a few years. +### Why a separate package? Originally, a major version of Provider was planned to ship, as a way to solve the aforementioned problems. But it was then decided against it, as this would have been @@ -159,16 +168,6 @@ Creating a separate package enabled: Indeed, Riverpod is designed to be the spiritual successor of Provider. Hence the name "Riverpod" (which is an anagram of "Provider"). -### The relationship between Riverpod and Provider -Still, conceptually, Riverpod and Provider are fairly similar. -Both packages fill a similar role. Both try to: - -- cache and dispose some stateful objects; -- offer a way to mock those objects during tests; -- offer a way for Widgets to listen to those objects in a simple way. - -You can think of Riverpod as what Provider could've been if it continued to mature for a few years. - ### The breaking change The only true downside of Riverpod is that it requires changing the widget type to work: