async_phase_notifier 0.0.1
async_phase_notifier: ^0.0.1 copied to clipboard
A variant of ValueNotifier that helps you better manage phases of asynchronous operations.
A variant of ValueNotifier that has AsyncPhase representing the initial /
waiting / complete / error phases of an asynchronous operation.
AsyncPhaseNotifier + AsyncPhase is similar to AsyncNotifier + AsyncValue of Riverpod.
Unlike AsyncNotifier and AsyncValue, which are tied to package:riverpod,
AsyncPhaseNotifier and AsyncPhase have no such binding. The notifier can be
used as just a handy variant of ValueNotifier with AsyncPhase as its value
and convenient methods for manipulating the phases.
Sample apps #
- Useless Facts - simple
- pub.dev explorer - advanced
Usage #
runAsync() #
The runAsync() method of AsyncPhaseNotifier executes an asynchronous function,
updates the value of AsyncPhaseNotifier automatically according to the phase of
the asynchronous operation, and notifies the listeners of those changes.
- The value of the notifier is switched to
AsyncWaitingwhen the operation starts. - The change is notified to listeners.
- The value is switched to either
AsyncCompleteorAsyncErrordepending on the result. - The change is notified to listeners.
final notifier = AsyncPhaseNotifier<int>();
notifier.runAsync((data) => someAsyncOperation());
AsyncPhase #
The value of AsyncPhaseNotifier is either AsyncInitial,
AsyncWaiting, AsyncComplete or AsyncError.
They are subtypes of AsyncPhase.
AsyncPhase provides the when() and whenOrNull() methods,
which are useful for choosing an action based on the current phase, like returning
an appropriate widget.
child: phase.when(
initial: (data) => Text('phase: AsyncInitial($data)'), // Optional
waiting: (data) => Text('phase: AsyncWaiting($data)'),
complete: (data) => Text('phase: AsyncComplete($data)'),
error: (data, error, stackTrace) => Text('phase: AsyncError($data, $error)'),
)
async_phase is a separate package, contained in this package. See
its document for details not covered here.
Listening for errors #
listenError()
You can listen for errors to imperatively trigger some action when the phase is
turned into AsyncError by runAsync(), like showing an AlertDialog or a SnackBar,
or to just log them.
final notifier = AsyncPhaseNotifier<Auth>();
final removeErrorListener = notifier.listenError((e, s) { ... });
...
// Remove the listener if it is no longer necessary.
removeErrorListener();
AsyncErrorListener
It is also possible to use AsyncErrorListener to listen for errors. The onError
callback is called when an asynchronous operation results in failure.
child: AsyncErrorListener(
notifier: notifier,
onError: (context, error, stackTrace) {
ScaffoldMessenger.of(context).showMaterialBanner(...);
},
child: ...,
)
A listener is added per each AsyncErrorListener. Please note that if you use this
widget at multiple places for a single notifier, the callback functions of all those
widgets are called on error.
Examples #
The examples here illustrate how to show a particular UI component depending on the phase of an asynchronous operation.
class WeatherNotifier extends AsyncPhaseNotifier<Weather> {
WeatherNotifier();
final repository = WeatherRepository();
void fetch() {
runAsync((weather) => repository.fetchWeather(Cities.tokyo));
}
}
Above is an AsyncPhaseNotifier that fetches the weather info of a city and notifies
its listeners. We'll see in the examples below how the notifier is used in combination
with each of the several ways to rebuild a widget.
With ValueListenableBuilder #
final notifier = WeatherNotifier();
notifier.fetch();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<AsyncPhase<Weather>>(
valueListenable: notifier,
builder: (context, phase, _) {
// Shows a progress indicator while fetching and
// either the result or an error when finished.
return phase.when(
waiting: (weather) => const CircularProgressIndicator(),
complete: (weather) => Text('$weather'),
error: (weather, e, s) => Text('$e'),
);
},
);
}
Or you can use AnimatedBuilder in a similar way.
With Provider #
final notifier = WeatherNotifier();
...
ValueListenableProvider<AsyncPhase<Weather>>.value(
value: notifier,
child: MaterialApp(home: ...),
)
...
notifier.fetch();
@override
Widget build(BuildContext context) {
final phase = context.watch<AsyncPhase<Weather>>();
return phase.when(
waiting: (weather) => const CircularProgressIndicator(),
complete: (weather) => Text('$weather'),
error: (weather, e, s) => Text('$e'),
);
}
With Grab #
final notifier = WeatherNotifier();
notifier.fetch();
@override
Widget build(BuildContext context) {
final phase = context.grab<AsyncPhase<Weather>>(notifier);
return phase.when(
waiting: (weather) => const CircularProgressIndicator(),
complete: (weather) => Text('$weather'),
error: (weather, e, s) => Text('$e'),
);
}
TODO #
- ❌ Add API documents
- ❌ Write tests