async_phase_notifier 0.1.0 async_phase_notifier: ^0.1.0 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
AsyncWaiting
when the operation starts. - The change is notified to listeners.
- The value is switched to either
AsyncComplete
orAsyncError
depending 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();
notifier.fetch();
ValueListenableProvider<AsyncPhase<Weather>>.value(
value: notifier,
child: MaterialApp(home: ...),
)
@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