website/docs/how_to/pull_to_refresh.mdx
import { Link } from "/src/components/Link"; import { AutoSnippet, When } from "/src/components/CodeSnippet"; import activity from "./pull_to_refresh/activity"; import fetchActivity from "./pull_to_refresh/fetch_activity"; import displayActivity from "!!raw-loader!./pull_to_refresh/display_activity.dart"; import displayActivity2 from "!!raw-loader!./pull_to_refresh/display_activity2.dart"; import displayActivity3 from "!!raw-loader!./pull_to_refresh/display_activity3.dart"; import displayActivity4 from "!!raw-loader!./pull_to_refresh/display_activity4.dart"; import fullApp from "./pull_to_refresh/full_app";
Riverpod natively supports pull-to-refresh thanks to its declarative nature.
In general, pull-to-refreshes can be complex due as there are multiple problems to solve:
Let's see how to solve this using Riverpod.
For this, we will make a simple example which recommends a random activity to users.
And doing a pull-to-refresh will trigger a new suggestion:
Before implement a pull-to-refresh, we first need something to refresh.
We can make a simple application which uses Bored API
to suggests a random activity to users.
First, let's define an Activity class:
<AutoSnippet {...activity} />
That class will be responsible for representing a suggested activity
in a type-safe manner, and handle JSON encoding/decoding.
Using Freezed/json_serializable is not required, but it is recommended.
Now, we'll want to define a provider making a HTTP GET request to fetch a single activity:
<AutoSnippet {...fetchActivity} />
We can now use this provider to display a random activity.
For now, we will not handle the loading/error state, and simply
display the activity when available:
RefreshIndicatorNow that we have a simple application, we can add a RefreshIndicator to it.
That widget is an official Material widget responsible for displaying a refresh indicator
when the user pulls down the screen.
Using RefreshIndicator requires a scrollable surface. But so far, we don't have
any. We can fix that by using a ListView/GridView/SingleChildScrollView/etc:
Users can now pull down the screen. But our data isn't refreshed yet.
When users pull down the screen, RefreshIndicator will invoke
the onRefresh callback. We can use that callback to refresh our data.
In there, we can use ref.refresh to refresh the provider of our choice.
Note: onRefresh is expected to return a Future.
And it is important for that future to complete when the refresh is done.
To obtain such a future, we can read our provider's .future property.
This will return a future which completes when our provider has resolved.
We can therefore update our RefreshIndicator to look like this:
At the moment, our UI does not handle the error/loading states.
Instead the data magically pops up when the loading/refresh is done.
Let's change this by gracefully handling those states. There are two cases:
Fortunately, when listening to an asynchronous provider in Riverpod,
Riverpod gives us an AsyncValue, which offers everything we need.
That AsyncValue can then be combined with Dart 3.0's pattern matching
as follows:
:::caution
We use valueOrNull here, as currently, using value throws
if in error/loading state.
Riverpod 3.0 will change this to have value behave like valueOrNull.
But for now, let's stick to valueOrNull.
:::
:::tip
Notice the usage of the :final valueOrNull? syntax in our pattern matching.
This syntax can be used only because activityProvider returns a non-nullable
Activity.
If your data can be null, you can instead use AsyncValue(hasData: true, :final valueOrNull).
This will correctly handle cases where the data is null, at the cost of
a few extra characters.
:::
Here is the combined source of everything we've covered so far:
<AutoSnippet {...fullApp} />