Riverpod and FutureProvider
Learning objectives
- You know how to use Riverpod's FutureProvider widget.
FutureProvider is a Riverpod widget for handling asynchronous data processing and showing the end result in a user interface. As pointed out in the documentation of FutureProvider, it can be considered as a combination of Provider
and FutureBuilder
.
An instance of a FutureProvider
returns the result of an asynchronous operation. As an example, we could create a FutureProvider
that returns Joke
instances by wrapping an API call in the provider.
final jokeFutureProvider = FutureProvider<Joke>((ref) async {
return await JokeService().getRandomJoke();
});
Listening to the FutureProvider
returns an instance of an AsyncValue widget, which is a FutureBuilder
-like utility that are handled over three cases: (1) when the future is still being completed (i.e., it is loading
), (2) when there is an error
, and (3) when the future is completed and there is data
. For the above jokeFutureBuilder
, the AsyncValue
returned from listening to the jokeFutureBuilder
could be handled as follows.
class SimpleJokeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final jokeFuture = ref.watch(jokeFutureProvider);
return jokeFuture.when(
loading: () => const Text("Loading..."),
error: (err, stack) => const Text("Error loading joke"),
data: (joke) => Column(children: [
Text(joke.setup),
Text(joke.punchline),
]));
}
}
The following program outlines a simple version of the joke application that uses the FutureBuilder
, showing a joke when the application is loaded. Click it open to see the full version. When you run the program, it retrieves a joke. To see another joke, you need to rerun the program.
To re-retrieve the data in the FutureProvider
, we use the refresh method from WidgetRef, which forces the provider to re-evaluate the state and return the new value. As an example, we could have a button that, when clicked, calls the refresh
method, fetching a new joke.
ElevatedButton(
onPressed: () => ref.refresh(jokeFutureProvider),
child: const Text('Fetch joke!'),
),
Finally, to avoid clicking the button when a joke is being loaded, when can utilize the isLoading property of AsyncValue
. It is a boolean that describes whether content is currently being loaded. If yes, we can display a Text
widget stating that the content is loading, and otherwise show the button and the joke.
A widget handling all of this would look as follows.
class JokeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final jokeFuture = ref.watch(jokeFutureProvider);
if (jokeFuture.isLoading) {
return const Text("Loading...");
}
return Column(children: [
ElevatedButton(
onPressed: () => ref.refresh(jokeFutureProvider),
child: const Text('Fetch joke!'),
),
jokeFuture.when(
loading: () => const Text("Loading..."),
error: (err, stack) => const Text("Error loading joke"),
data: (joke) => Column(children: [
Text(joke.setup),
Text(joke.punchline),
]))
]);
}
}
The application as a whole is available below. Click Open code in editor
to display the code in the editor and to try the application out.