Skip to content

Commit

Permalink
Grammatical and stylistic modifications to docs (#3103)
Browse files Browse the repository at this point in the history
I made grammatical and stylistic modifications to the `Essentials`
portion of the documentation.

--

I was reading through the documentation for the first time as a new user
and thought some of my changes could be useful. If I was overzealous, I
apologize.

I'm excited to begin using the `riverpod` package in my project. Thank
you the time that's been spent on this project.
  • Loading branch information
rhinck authored Nov 13, 2023
1 parent 48b88b8 commit fb3599f
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 52 deletions.
16 changes: 8 additions & 8 deletions website/docs/essentials/auto_dispose.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -24,7 +24,7 @@ This ranges from delaying the disposal of state to reacting to destruction.
<When codegen={true}>

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.

Expand All @@ -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:

Expand All @@ -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.
:::
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -162,6 +162,6 @@ Then, we can use it like so:

<AutoSnippet {...cacheForUsage} />

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.
4 changes: 2 additions & 2 deletions website/docs/essentials/auto_dispose/keep_alive/raw.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
final provider = FutureProvider.autoDispose<String>((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:
Expand Down
10 changes: 5 additions & 5 deletions website/docs/essentials/do_dont.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ 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.

A good portion of these recommendations can be enforced with [riverpod_lint](https://pub.dev/packages/riverpod_lint).
See <Link documentID="introduction/getting_started" hash="enabling-riverpod_lintcustom_lint"/>
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.
Expand All @@ -40,8 +40,8 @@ class WidgetState extends State<MyWidget> {
**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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 4 additions & 5 deletions website/docs/essentials/eager_initialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand All @@ -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`:

<AutoSnippet raw={asyncConsumerExample} />
Expand Down
50 changes: 25 additions & 25 deletions website/docs/essentials/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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 <Link documentID="essentials/side_effects" />).
And then have your logic be a method of that `Notifier`.
into a `Notifier` (see <Link documentID="essentials/side_effects" />),
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,
Expand All @@ -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?
Expand All @@ -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.
Expand All @@ -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?

Expand Down
14 changes: 7 additions & 7 deletions website/docs/essentials/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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`.
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down

0 comments on commit fb3599f

Please sign in to comment.