From 33baec8afbd9a51da909f4ae6a780fc2d6bdcb1e Mon Sep 17 00:00:00 2001 From: Robert Hinckley <37898062+rhinck@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:33:50 -0700 Subject: [PATCH] Documentation modifications Made grammatical and stylistic modifications to the documentation --- website/docs/essentials/auto_dispose.mdx | 16 +++--- .../auto_dispose/keep_alive/raw.dart | 4 +- website/docs/essentials/do_dont.mdx | 10 ++-- .../docs/essentials/eager_initialization.mdx | 9 ++-- website/docs/essentials/faq.mdx | 50 +++++++++---------- website/docs/essentials/testing.mdx | 14 +++--- 6 files changed, 51 insertions(+), 52 deletions(-) diff --git a/website/docs/essentials/auto_dispose.mdx b/website/docs/essentials/auto_dispose.mdx index 62a0995aa..e2ee89fcf 100644 --- a/website/docs/essentials/auto_dispose.mdx +++ b/website/docs/essentials/auto_dispose.mdx @@ -14,7 +14,7 @@ import cacheForUsage from "./auto_dispose/cache_for_usage"; import invalidateFamilyExample from './auto_dispose/invalidate_family_example' So far, we've seen how to create/update some state. -But we have yet to talk about when state destruction. +But we have yet to talk about when state destruction occurs. Riverpod offers various ways to interact with state disposal. This ranges from delaying the disposal of state to reacting to destruction. @@ -24,7 +24,7 @@ This ranges from delaying the disposal of state to reacting to destruction. When using code-generation, by default, the state is destroyed when -the provider stops being listened. +the provider stops being listened to. This happens when a listener has no active listener for a full frame. When that happens, the state is destroyed. @@ -43,7 +43,7 @@ when the provider stops being listened. You can optionally change this behavior and use automatic disposal. When doing so, Riverpod will track whether a provider has listeners or not. -Then, if for a full frame the provider has no listeners, the state will be destroyed. +Then, if the provider has no listeners for a full frame, the state will be destroyed. To enable automatic disposal, you can use `.autoDispose` next to the provider type: @@ -58,7 +58,7 @@ The state will always be destroyed when the provider is recomputed. ::: :::caution -When providers receive parameters, it is recommended to enable the automatic disposal. +When providers receive parameters, it is recommended to enable automatic disposal. That is because otherwise, one state per parameter combination will be created, which can lead to memory leaks. ::: @@ -96,7 +96,7 @@ There are other useful life-cycles such as: :::info You can call `ref.onDispose` as many times as you wish. Feel free to call it once per disposable object in your provider. This practice -makes it easier to spot when we forgot to dispose of something. +makes it easier to spot when we forget to dispose of something. ::: ## Manually forcing the destruction of a provider, using `ref.invalidate` @@ -162,6 +162,6 @@ Then, we can use it like so: -This logic can be tweaked to fit your needs. For instance. -For example you could use `ref.onCancel`/`ref.onResume` to destroy the state -only if a provider hasn't been listened for a specific amount of time. +This logic can be tweaked to fit your needs. +For example, you could use `ref.onCancel`/`ref.onResume` to destroy the state +only if a provider hasn't been listened to for a specific amount of time. diff --git a/website/docs/essentials/auto_dispose/keep_alive/raw.dart b/website/docs/essentials/auto_dispose/keep_alive/raw.dart index 356abd920..f0fc6a3ba 100644 --- a/website/docs/essentials/auto_dispose/keep_alive/raw.dart +++ b/website/docs/essentials/auto_dispose/keep_alive/raw.dart @@ -7,8 +7,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; final provider = FutureProvider.autoDispose((ref) async { final response = await http.get(Uri.parse('https://example.com')); // We keep the provider alive only after the request has successfully completed. - // If the request failed (and threw), then when the provider stops being - // listened, the state will be destroyed. + // If the request failed (and threw an exception), then when the provider stops being + // listened to, the state will be destroyed. final link = ref.keepAlive(); // We can use the `link` to restore the auto-dispose behavior with: diff --git a/website/docs/essentials/do_dont.mdx b/website/docs/essentials/do_dont.mdx index 126d7af7d..e1aa17494 100644 --- a/website/docs/essentials/do_dont.mdx +++ b/website/docs/essentials/do_dont.mdx @@ -9,7 +9,7 @@ To ensure good maintainability of your code, here is a list of good practices you should follow when using Riverpod. This list is not exhaustive, and is subject to change. -If you have any suggestion, feel free to [open an issue](https://github.com/rrousselGit/riverpod/issues/new?assignees=rrousselGit&labels=documentation%2C+needs+triage&projects=&template=example_request.md&title=). +If you have any suggestions, feel free to [open an issue](https://github.com/rrousselGit/riverpod/issues/new?assignees=rrousselGit&labels=documentation%2C+needs+triage&projects=&template=example_request.md&title=). Items in this list are not in any particular order. @@ -17,7 +17,7 @@ A good portion of these recommendations can be enforced with [riverpod_lint](htt See for installation instructions. -## AVOID having to intialize providers in a widget +## AVOID initializing providers in a widget Providers should initialize themselves. They should not be initialized by an external element such as a widget. @@ -40,8 +40,8 @@ class WidgetState extends State { **CONSIDER** There is no "one-size fits all" solution to this problem. -But if your initialization logic depends on factors external to the provider, -often, the correct place to put such logic is in the `onPressed` of a button +If your initialization logic depends on factors external to the provider, +often the correct place to put such logic is in the `onPressed` method of a button triggering navigation: ```dart @@ -71,7 +71,7 @@ One reason why this is discouraged is that such state is often scoped to a route Failing to do so could break your app's back button, due to a new page overriding the state of a previous page. -## DON'T perform side effects during the initialisation of a provider +## DON'T perform side effects during the initialization of a provider Providers should generally be used to represent a "read" operation. You should not use them for "write" operations, such as submitting a form. diff --git a/website/docs/essentials/eager_initialization.mdx b/website/docs/essentials/eager_initialization.mdx index c7047e92e..90c586868 100644 --- a/website/docs/essentials/eager_initialization.mdx +++ b/website/docs/essentials/eager_initialization.mdx @@ -14,7 +14,7 @@ initialized when it is first used. This is useful for providers that are only used in certain parts of the application. Unfortunately, there is no way to flag a provider as needing to be eagerly initialized due -to how Dart works (for tree shaking purposes). One solution howerver is to forcibly +to how Dart works (for tree shaking purposes). One solution, however, is to forcibly read the providers you want to eagerly initialize at the root of your application. The recommended approach is to simply "watch" a provider in a Consumer placed right under your `ProviderScope`: @@ -36,15 +36,14 @@ is a separate widget, which does nothing but return a `child`. The key part is that it returns a `child`, rather than instantiating `MaterialApp` itself. This means that if `_EagerInitialization` ever rebuilds, the `child` variable -will be not have changed. And when a widget doesn't change, Flutter doesn't rebuild it. +will not have changed. And when a widget doesn't change, Flutter doesn't rebuild it. -As such, `_EagerInitialization` will rebuild but nothing else (unless another widget -listens to that provider, of course). +As such, only `_EagerInitialization` will rebuild, unless another widget is also listening to that provider. #### Using this approach, how can I handle loading and error states? You can handle loading/error states as you normally would in a `Consumer`. -Your `_EagerInitialization` could check if a provider is in "loading" state, +Your `_EagerInitialization` could check if a provider is in a "loading" state, and if so, return a `CircularProgressIndicator` instead of the `child`: diff --git a/website/docs/essentials/faq.mdx b/website/docs/essentials/faq.mdx index 2c39515cc..1ddad482a 100644 --- a/website/docs/essentials/faq.mdx +++ b/website/docs/essentials/faq.mdx @@ -38,8 +38,8 @@ Whereas if we call `invalidate` but don't read it right after, then the update will trigger _later_. That "later" update is generally at the start of the next frame. -Yet if invalidating a provider which is currently not listened, -then the provider will not be updated until it is listened again. +Yet, if a provider that is currently not being listened to is invalidated, +it will not be updated until it is listened to again. ## Why is there no shared interface between Ref and WidgetRef? @@ -59,26 +59,26 @@ It is effectively putting your logic in the UI layer, which is not recommended. Such code should be refactored to **always** use `Ref`. The solution to this problem is typically to move your logic -into a `Notifier` (see ). -And then have your logic be a method of that `Notifier`. +into a `Notifier` (see ), +and then have your logic be a method of that `Notifier`. This way, when your widgets want to invoke this logic, they can -write something among the line of: +write something along the lines of: ```dart ref.read(yourNotifierProvider.notifier).yourMethod(); ``` -And `yourMethod` would use the `Notifier`'s` `Ref` to interact with other providers. +`yourMethod` would use the `Notifier`'s `Ref` to interact with other providers. ## Why do we need to extend ConsumerWidget instead of using the raw StatelessWidget? -This is due to an unfortunate limitation in the API of `InheritedWidget`s. +This is due to an unfortunate limitation in the API of `InheritedWidget`. There are a few problems: - It is not possible to implement an "on change" listener with `InheritedWidget`. - Meaning that something such as `ref.listen` cannot be used with `BuildContext`. + That means that something such as `ref.listen` cannot be used with `BuildContext`. `State.didChangeDependencies` is the closest thing to it, but it is not reliable. One issue is that the life-cycle can be triggered even if no dependency changed, @@ -87,21 +87,21 @@ There are a few problems: - Widgets listening to an `InheritedWidget` never stop listening to it. This is usually fine for pure metadata, such as "theme" or "media query". - But for business logic, this is a problem. + For business logic, this is a problem. Say you use a provider to represent a paginated API. - As the page offset changes, you wouldn't want your widget to keep listening + When the page offset changes, you wouldn't want your widget to keep listening to the previously visible pages. - `InheritedWidget` has no way to track when widgets stop listening to them. - Riverpod sometimes rely on tracking whether a provider is listened to or not. + Riverpod sometimes relies on tracking whether or not a provider is being listened to. -Those are crutial for both the auto dispose mechanism and the ability to +This functionality is crucial for both the auto dispose mechanism and the ability to pass arguments to providers. -And those features are what makes Riverpod so powerful. +Those features are what make Riverpod so powerful. -Maybe in a distant future, those issues would be fixed. In that case, +Maybe in a distant future, those issues will be fixed. In that case, Riverpod would migrate to using `BuildContext` instead of `Ref`. -That would enable using `StatelessWidget` instead of `ConsumerWidget`. +This would enable using `StatelessWidget` instead of `ConsumerWidget`. But that's for another time! ## Why doesn't hooks_riverpod exports flutter_hooks? @@ -117,23 +117,23 @@ in one but not the other. Notifiers use `identical` instead of `==` to filter updates. This is because it is quite common for Riverpod users to also use a code-generator -such as Freezed/built_value for the sake of a copyWith implementation. But they -also override == to deeply compare objects. But a deep object comparison is quite costly. +such as Freezed/built_value for the sake of a copyWith implementation. Those packages +override `==` to deeply compare objects. A deep object comparison is quite costly. "Business logic" models tend to have lots of properties. Worse, they also have collections -(lists/maps/...). +such as lists, maps, and so on. At the same time, when using complex "business" objects, most `state = newState` invocations -always result in a notification (otherwise no point in calling the setter). Generally, the main +always result in a notification (otherwise there is no point in calling the setter). Generally, the main case where we call `state = newState` when the current state and new states are equal is for primitive objects (ints, enums, strings, but not lists/classes/...). -But these objects are "canonicalized by default". So if such objects are equal, +These objects are "canonicalized by default". If such objects are equal, they generally are also "identical". -Riverpod using identical to filter updates is therefore an attempt at having +Riverpod using `identical` to filter updates is therefore an attempt at having a good default for both worlds. For complex objects where we don't really care -about filtering objects and where == may be expensive due to code-generators +about filtering objects and where `==` may be expensive due to code-generators generating an `==` override by default, using `identical` provides an efficient way of notifying listeners. -At the same time, for simple objects, `identical` correctly does filter redundant notifications. +At the same time, for simple objects, `identical` does correctly filter redundant notifications. Last but not least, you can change this behavior by overriding the method `updateShouldNotify` on Notifiers. @@ -147,11 +147,11 @@ at once will often reset providers that you did not intend to reset. This is commonly asked by users who want to reset the state of their application when the user logs out. -If this is what you are after, instead you should have everything dependent on the +If this is what you are after, you should instead have everything dependent on the user's state to `ref.watch` the "user" provider. Then, when the user logs out, all providers depending on it would automatically -be reset. But everything else will stay untouched. +be reset but everything else would remain untouched. ## I have the error "Cannot use "ref" after the widget was disposed", what's wrong? diff --git a/website/docs/essentials/testing.mdx b/website/docs/essentials/testing.mdx index 74cc8a5e6..61b068f91 100644 --- a/website/docs/essentials/testing.mdx +++ b/website/docs/essentials/testing.mdx @@ -17,11 +17,11 @@ import notifierMock from "./testing/notifier_mock"; A core part of the Riverpod API is the ability to test your providers in isolation. -For a proper test suites, there are a few challenges to overcome: +For a proper test suite, there are a few challenges to overcome: - Tests should not share state. This means that new tests should not be affected by the previous tests. -- Tests should give us the ability to mock certain functionalities, +- Tests should give us the ability to mock certain functionalities to achieve the desired state. - The test environment should be as close as possible to the real environment. @@ -35,7 +35,7 @@ When defining a test with Riverpod, there are two main scenarios: - Unit tests, usually with no Flutter dependency. This can be useful for testing the behavior of a provider in isolation. - Widget tests, usually with a Flutter dependency. - This can be useful for behavior of a widget using a provider. + This can be useful for testing the behavior of a widget that uses a provider. ### Unit tests @@ -61,7 +61,7 @@ Now that we have a ProviderContainer, we can use it to read providers using: :::caution Be careful when using `container.read` when providers are automatically disposed. -If your provider is not listened, chances are its state will get destroyed +If your provider is not listened to, chances are that its state will get destroyed in the middle of your test. In that case, consider using `container.listen`. @@ -96,7 +96,7 @@ We can then use it to read providers. Here's a full example: ## Mocking providers -So far, we've seen how to setup a test and to basic interactions with providers. +So far, we've seen how to set up a test and basic interactions with providers. However, in some cases, we may want to mock a provider. The cool part: All providers can be mocked by default, without any additional setup. @@ -122,10 +122,10 @@ You can then combine this with packages such as [mockito](https://pub.dev/packag or [mocktail](https://pub.dev/packages/mocktail) to use their `verify` API. Or more simply, you can add all changes in a list and assert on it. -## Awaiting for asynchronous providers +## Awaiting asynchronous providers In Riverpod, it is very common for providers to return a Future/Stream. -In that case, chances are our tests wants to await for that asynchronous operation +In that case, chances are that our tests need to await for that asynchronous operation to be completed. One way to do so is to read the `.future` of a provider: