mirror of
https://github.com/rrousselGit/riverpod.git
synced 2025-08-15 02:06:53 +08:00
347 lines
12 KiB
Plaintext
347 lines
12 KiB
Plaintext
---
|
|
title: Make your first provider/network request
|
|
pagination_prev: introduction/getting_started
|
|
version: 3
|
|
---
|
|
|
|
import { Link } from "/src/components/Link";
|
|
import { AutoSnippet } from "/src/components/CodeSnippet";
|
|
import activity from "./first_request/activity";
|
|
import provider from "./first_request/provider";
|
|
import consumer from "./first_request/consumer";
|
|
import consumerWidget from "./first_request/consumer_widget";
|
|
import consumerStatefulWidget from "./first_request/consumer_stateful_widget";
|
|
import hookConsumerWidget from "./first_request/hook_consumer_widget";
|
|
import Legend from "./first_request/legend/legend";
|
|
import Tabs from "@theme/Tabs";
|
|
import TabItem from "@theme/TabItem";
|
|
|
|
Network requests are the core of any application. But there are a lot of things to consider when
|
|
making a network request:
|
|
|
|
- The UI should render a loading state while the request is being made
|
|
- Errors should be gracefully handled
|
|
- The request should be cached if possible
|
|
|
|
In this section, we will see how Riverpod can help us deal with all of this naturally.
|
|
|
|
## Setting up `ProviderScope`
|
|
|
|
Before we start making network requests, make sure that `ProviderScope` is added at the
|
|
root of the application.
|
|
|
|
```dart
|
|
void main() {
|
|
runApp(
|
|
// To install Riverpod, we need to add this widget above everything else.
|
|
// This should not be inside "MyApp" but as direct parameter to "runApp".
|
|
ProviderScope(
|
|
child: MyApp(),
|
|
),
|
|
);
|
|
}
|
|
```
|
|
|
|
Doing so will enable Riverpod for the entire application.
|
|
|
|
:::note
|
|
For complete installation steps (such as installing [riverpod_lint](https://pub.dev/packages/riverpod_lint)
|
|
and running the code-generator), check out <Link documentID="introduction/getting_started" />.
|
|
:::
|
|
|
|
## Performing your network request in a "provider"
|
|
|
|
Performing a network request is usually what we call "business logic".
|
|
In Riverpod, business logic is placed inside "providers".
|
|
A provider is a super-powered function.
|
|
They behave like normal functions, with the added benefits of:
|
|
|
|
- Being cached
|
|
- Offering default error/loading handling
|
|
- Being listenable
|
|
- Automatically re-executing when some data changes
|
|
|
|
This make providers a perfect fit for _GET_ network requests (as for _POST/etc_ requests, see <Link documentID="essentials/side_effects" />).
|
|
|
|
As an example, let's make a simple application which suggests a random activity to do when bored.
|
|
To do so, we will use the [Bored API](https://boredapi.com/). In particular,
|
|
we will perform a _GET_ request on the `/api/activity` endpoint. This returns a JSON object,
|
|
which we will parse into a Dart class instance.
|
|
The next step would then be to display this activity in the UI. We would also make sure
|
|
to render a loading state while the request is being made, and to gracefully handle errors.
|
|
|
|
Sounds great? Let's do it!
|
|
|
|
### Defining the model
|
|
|
|
Before we start, we need to define the model of the data we will receive from the API.
|
|
This model will also need a way to parse the JSON object into a Dart class instance.
|
|
|
|
Generally, it is recommended to use a code-generator such as [Freezed](https://pub.dev/packages/freezed)
|
|
or [json_serializable](https://pub.dev/packages/json_serializable) to handle
|
|
JSON decoding. But of course, it's also possible to do it by hand.
|
|
|
|
Anyway, here's our model:
|
|
|
|
<AutoSnippet title="activity.dart" {...activity} />
|
|
|
|
### Creating the provider
|
|
|
|
Now that we have our model, we can start querying the API.
|
|
To do so, we will need to create our first provider.
|
|
|
|
The syntax for defining a provider is as followed:
|
|
|
|
<Tabs>
|
|
<TabItem value="riverpod" label="riverpod">
|
|
<Legend
|
|
code={`
|
|
final name = SomeProvider.someModifier<Result>((ref) {
|
|
<your logic here>
|
|
});
|
|
`}
|
|
annotations={[
|
|
{
|
|
offset: 6,
|
|
length: 4,
|
|
label: "The provider variable",
|
|
description: <>
|
|
|
|
This variable is what will be used to interact with our provider.
|
|
The variable must be final and "top-level" (global).
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 13,
|
|
length: 12,
|
|
label: "The provider type",
|
|
description: <>
|
|
|
|
Generally either `Provider`, `FutureProvider` or `StreamProvider`.
|
|
The type of provider used depends on the return value of your function.
|
|
For example, to create a `Future<Activity>`, you'll want a `FutureProvider<Activity>`.
|
|
|
|
`FutureProvider` is the one you'll want to use the most.
|
|
|
|
:::tip
|
|
Don't think in terms of "Which provider should I pick".
|
|
Instead, think in terms of "What do I want to return". The provider type
|
|
will follow naturally.
|
|
:::
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 25,
|
|
length: 13,
|
|
label: "Modifiers (optional)",
|
|
description: <>
|
|
|
|
Often, after the type of the provider you may see a "modifier".
|
|
Modifiers are optional, and are used to tweak the behavior of the provider
|
|
in a way that is type-safe.
|
|
|
|
There are currently two modifiers available:
|
|
|
|
- `autoDispose`, which will automatically clear the cache when the provider
|
|
stops being used.
|
|
See also <Link documentID="essentials/auto_dispose" />
|
|
- `family`, which enables passing arguments to your provider.
|
|
See also <Link documentID="essentials/passing_args" />.
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 48,
|
|
length: 3,
|
|
label: "Ref",
|
|
description: <>
|
|
|
|
An object used to interact with other providers.
|
|
All providers have one; either as parameter of the provider function,
|
|
or as a property of a Notifier.
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 57,
|
|
length: 17,
|
|
label: "The provider function",
|
|
description: <>
|
|
|
|
This is where we place the logic of our providers.
|
|
This function will be called when the provider is first read.
|
|
Subsequent reads will not call the function again, but instead return the cached value.
|
|
|
|
</>
|
|
},
|
|
]}
|
|
/>
|
|
</TabItem>
|
|
<TabItem value="riverpod_generator" label="riverpod_generator">
|
|
<Legend
|
|
code={`
|
|
@riverpod
|
|
Result myFunction(Ref ref) {
|
|
<your logic here>
|
|
}
|
|
`}
|
|
annotations={[
|
|
{
|
|
offset: 0,
|
|
length: 9,
|
|
label: "The annotation",
|
|
description: <>
|
|
|
|
All providers must be annotated with `@riverpod` or `@Riverpod()`.
|
|
This annotation can be placed on global functions or classes.
|
|
Through this annotation, it is possible to configure the provider.
|
|
|
|
For example, we can disable "auto-dispose" (which we will see later) by writing `@Riverpod(keepAlive: true)`.
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 17,
|
|
length: 10,
|
|
label: "The annotated function",
|
|
description: <>
|
|
|
|
The name of the annotated function determines how the provider
|
|
will be interacted with.
|
|
For a given function `myFunction`, a generated `myFunctionProvider` variable will be generated.
|
|
|
|
Annotated functions **must** specify a "ref" as first parameter.
|
|
Besides that, the function can have any number of parameters, including generics.
|
|
The function is also free to return a `Future`/`Stream` if it wishes to.
|
|
|
|
This function will be called when the provider is first read.
|
|
Subsequent reads will not call the function again, but instead return the cached value.
|
|
|
|
</>
|
|
},
|
|
{
|
|
offset: 28,
|
|
length: 7,
|
|
label: "Ref",
|
|
description: <>
|
|
|
|
An object used to interact with other providers.
|
|
All providers have one; either as parameter of the provider function,
|
|
or as a property of a Notifier.
|
|
The type of this object is determined by the name of the function/class.
|
|
|
|
</>
|
|
},
|
|
]}
|
|
/>
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
In our case, we want to _GET_ an activity from the API.
|
|
Since a _GET_ is an asynchronous operation, that means we will want
|
|
to create a `Future<Activity>`.
|
|
|
|
Using the syntax defined previously, we can therefore define our provider as followed:
|
|
|
|
<AutoSnippet title="provider.dart" {...provider} />
|
|
|
|
In this snippet, we've defined a provider named `activityProvider` which
|
|
our UI will be able to use to obtain a random activity. It is worth noting
|
|
that:
|
|
|
|
- The network request will not be executed until the UI reads the provider
|
|
at least once.
|
|
- Subsequent reads will not re-execute the network request,
|
|
but instead return the previously fetched activity.
|
|
- If the UI stops using this provider, the cache will be destroyed.
|
|
Then, if the UI ever uses the provider again, that a new network request will be made.
|
|
- We did not catch errors. This is voluntary, as providers
|
|
natively handle errors.
|
|
If the network request or if the JSON parsing throws, the error
|
|
will be caught by Riverpod. Then, the UI will automatically have the necessary
|
|
information to render an error page.
|
|
|
|
:::info
|
|
Providers are "lazy". Defining a provider will not execute the network request.
|
|
Instead, the network request will be executed when the provider is first read.
|
|
:::
|
|
|
|
### Rendering the network request's response in the UI
|
|
|
|
Now that we have defined a provider, we can start using it inside our UI
|
|
to display the activity.
|
|
|
|
To interact with a provider, we need an object called "ref". You may have seen
|
|
it previously in the provider definition, as providers naturally have access
|
|
to a "ref" object.
|
|
But in our case, we aren't in a provider, but a widget. So how do we get a "ref"?
|
|
|
|
The solution is to use a custom widget called `Consumer`. A `Consumer` is a widget
|
|
similar to `Builder`, but with the added benefit of offering us a "ref". This enables our UI to read providers.
|
|
The following example showcases how to use a `Consumer`:
|
|
|
|
<AutoSnippet title="consumer.dart" {...consumer} />
|
|
|
|
In that snippet, we've used a `Consumer` to read our `activityProvider` and display the activity.
|
|
We also gracefully handled the loading/error states.
|
|
Notice how the UI was able to handle loading/error states without having to do anything special
|
|
in the provider.
|
|
At the same time, if the widget were to rebuild, the network request would correctly
|
|
not be re-executed. Other widgets could also access the same provider without
|
|
re-executing the network request.
|
|
|
|
:::info
|
|
Widgets can listen to as many providers as they want. To do so, simply add more `ref.watch` calls.
|
|
:::
|
|
|
|
:::tip
|
|
Make sure to install the linter. That will enable your IDE to offer refactoring
|
|
options to automatically add a `Consumer` or convert a `StatelessWidget` into a `ConsumerWidget`.
|
|
|
|
See <Link documentID="introduction/getting_started" hash="enabling-riverpod_lintcustom_lint" /> for installation steps.
|
|
:::
|
|
|
|
## Going further: Removing code indentation by using `ConsumerWidget` instead of `Consumer`.
|
|
|
|
In the previous example, we used a `Consumer` to read our provider.
|
|
Although there is nothing wrong with this approach, the added indentation
|
|
can make the code harder to read.
|
|
|
|
Riverpod offers an alternative way of achieving the same result:
|
|
Instead of writing a `StatelessWidget`/`StatefulWidget` returns a `Consumer`, we can
|
|
define a `ConsumerWidget`/`ConsumerStatefulWidget`.
|
|
`ConsumerWidget` and `ConsumerStatefulWidget` are effectively the fusion
|
|
of a `StatelessWidget`/`StatefulWidget` and a `Consumer`. They behave the same
|
|
as their original counterpart, but with the added benefit of offering a "ref".
|
|
|
|
We can rewrite the previous examples to use `ConsumerWidget` as followed:
|
|
|
|
<AutoSnippet {...consumerWidget} />
|
|
|
|
As for `ConsumerStatefulWidget`, we would instead write:
|
|
|
|
<AutoSnippet {...consumerStatefulWidget} />
|
|
|
|
### Flutter_hooks considerations: Combining `HookWidget` and `ConsumerWidget`
|
|
|
|
:::caution
|
|
If you have never heard about "hooks" before, feel free to skip this section.
|
|
[Flutter_hooks](https://pub.dev/packages/flutter_hooks) is a package
|
|
independent from Riverpod but often used alongside it. If you are new to Riverpod,
|
|
using "hooks" is discouraged. See more in <Link documentID="concepts/about_hooks"/>.
|
|
:::
|
|
|
|
If you are using `flutter_hooks`, you may be wondering how to combine `HookWidget`
|
|
and `ConsumerWidget`. After all, both involve changing the extended widget class.
|
|
|
|
Riverpod offers a solution to this problem: `HookConsumerWidget` and `StatefulHookConsumerWidget`.
|
|
Similarly to how `ConsumerWidget` and `ConsumerStatefulWidget` are the fusion of `Consumer` and `StatelessWidget`/`StatefulWidget`,
|
|
`HookConsumerWidget` and `StatefulHookConsumerWidget` are the fusion of `Consumer` and `HookWidget`/`HookStatefulWidget`.
|
|
As such, they enable using both hooks and providers in the same widget.
|
|
|
|
To showcase this, we could one more time rewrite the previous example:
|
|
|
|
<AutoSnippet {...hookConsumerWidget} />
|