async_phase_notifier 0.4.3 async_phase_notifier: ^0.4.3 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, included in this package. See
its document for details not covered here.
Listening for phase changes #
listen()
With listen(), you can trigger some action when the phase or its data changes.
This is not much different from addListener()
, except for the following points:
- Returns a function to easily stop listening.
- Takes a listener function that receives the phase at the time of the call.
- The listener is called asynchronously because this method uses
Stream
internally.
final notifier = AsyncPhaseNotifier<Auth>();
final cancel = notifier.listen((phase) { /* Some action */ });
...
// Remove the listener if it is no longer necessary.
cancel();
listenFor()
With listenFor(), you can trigger some action in one of the callbacks relevant to the latest phase when the phase or its data changes.
Note:
- All callbacks are optional.
- Listener is not added if no callback function is passed.
- The
onWaiting
callback is called when the phase has changed toAsyncWaiting
and also fromAsyncWaiting
. A boolean value is passed to the callback to indicate the start or end of an asynchronous operation.
final notifier = AsyncPhaseNotifier<Auth>();
final cancel = notifier.listenFor(
onWaiting: (isWaiting) { /* e.g. Toggling an indicator */ },
onComplete: (data) { /* e.g. Logging the result of an operation */ },
onError: (e, s) { /* e.g. Showing an error dialog */ },
);
...
// Remove the listener if it is no longer necessary.
cancel();
AsyncPhaseListener
It is also possible to use the AsyncPhaseListener widget to listen for phase changes.
child: AsyncPhaseListener(
notifier: notifier,
onWaiting: (isWaiting) { /* e.g. Toggling an indicator */ },
onComplete: (data) { /* e.g. Logging the result of an operation */ },
onError: (e, s) { /* e.g. Showing an error dialog */ },
child: ...,
)
Please note that a listener is added per each AsyncPhaseListener
, not per
notifier. If this widget is used at various places for one certain notifier,
a single notification causes each of them to run its callback function.
Examples #
Here is WeatherNotifier extending AsyncPhaseNotifier
. It fetches the weather
info of a city and notifies its listeners.
class WeatherNotifier extends AsyncPhaseNotifier<Weather> {
WeatherNotifier();
final repository = WeatherRepository();
void fetch() {
runAsync((weather) => repository.fetchWeather(Cities.tokyo));
}
}
final notifier = WeatherNotifier();
notifier.fetch();
The examples below use this notifier and show a particular UI component corresponding to each phase of the fetch.
With ValueListenableBuilder #
@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
/ ListenableBuilder
in a similar way.
With Provider #
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 #
void main() {
runApp(
const Grab(child: App()),
);
}
@override
Widget build(BuildContext context) {
final phase = notifier.grab(context);
return phase.when(
waiting: (weather) => const CircularProgressIndicator(),
complete: (weather) => Text('$weather'),
error: (weather, e, s) => Text('$e'),
);
}
TODO #
- ❌ Add API documents
- ✅
Write tests