--- 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 . ::: ## 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 ). 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: ### 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: ((ref) { }); `} 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`, you'll want a `FutureProvider`. `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 - `family`, which enables passing arguments to your provider. See also . }, { 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. }, ]} /> } `} 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. }, ]} /> 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`. Using the syntax defined previously, we can therefore define our provider as followed: 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`: 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 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: As for `ConsumerStatefulWidget`, we would instead write: ### 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 . ::: 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: