updateType method
Future<AsyncPhase<T> >
updateType(
- Future<
Object?> func(), { - void onWaiting(
- T data
- void onComplete(
- T data
- void onError(
- Object e,
- StackTrace s
Updates the phase type while preserving the current data.
Transitions to AsyncWaiting at the start of the function, then transitions to the appropriate phase when the function finishes.
Use cases
- Running an asynchronous operation where the result data is not needed, but tracking the phase (e.g., showing/hiding a loading indicator) is necessary.
- Using
AsyncPhaseNotifierto implement a command-like approach, as discussed in the official Flutter architecture guide.- Steps:
- Create
AsyncPhaseNotifierinstances within a view model to represent specific actions. - Use
updateType()within a view model method to execute the operation associated with that action. - Listen to the
AsyncPhaseNotifiervalue in the UI to reflect the current phase.
- Create
- While this doesn't follow the formal Command pattern, it serves the same purpose of decoupling UI and business logic. This method simplifies the implementation of this approach.
- Steps:
Details
- Flexible return type: The callback
funccan return any type (Object?), not justT. - Phase-aware result: If the function returns an AsyncPhase,
the notifier's value is updated to match that specific phase.
- Example: If the notifier is
AsyncPhaseNotifier<int>and the function returnsAsyncError<String>, the value becomesAsyncError<int>, preserving the existing data but adopting the error state. - This allows the final phase to be AsyncInitial or AsyncWaiting if returned by the function, giving you full control over the resulting phase.
- Example: If the notifier is
final notifier = AsyncPhaseNotifier(123);
// Returns a normal value (non-AsyncPhase)
var phase = await notifier.updateType(() async => 'abc');
expect(phase.data, 123);
expect(phase, isA<AsyncComplete<int>>());
// Throws an error
phase = await notifier.updateType(() => throw Exception());
expect(phase.data, 123);
expect(phase, isA<AsyncError<int>>());
// Returns an AsyncPhase (AsyncComplete)
phase = await notifier.updateType(() async => AsyncComplete('abc'));
expect(phase.data, 123);
expect(phase, isA<AsyncComplete<int>>());
// Returns an AsyncPhase (AsyncError)
phase = await notifier.updateType(() async => AsyncError(error: ...));
expect(phase.data, 123);
expect(phase, isA<AsyncError<int>>());
Callbacks upon phase changes
The onWaiting, onComplete, and onError callbacks are called
when the asynchronous operation starts, completes successfully,
or fails, respectively. However, note that errors occurring in
those callbacks are not automatically handled.
If the function returns an AsyncPhase, the subsequent callbacks are triggered based on that resulting phase:
- AsyncWaiting:
- The
onWaitingcallback is not called at the conclusion of this method because the phase remains AsyncWaiting, which was already set at the start of the operation.
- The
- AsyncComplete:
- The
onCompletecallback is called.
- The
- AsyncError:
- The
onErrorcallback is called. This occurs whether the function explicitly returned an AsyncError or threw an exception.
- The
Implementation
Future<AsyncPhase<T>> updateType(
Future<Object?> Function() func, {
void Function(T data)? onWaiting,
void Function(T data)? onComplete,
void Function(Object e, StackTrace s)? onError,
}) async {
value = value.copyAsWaiting();
onWaiting?.call(data);
AsyncPhase<Object?> phase;
try {
final result = await func();
if (result is AsyncPhase) {
phase = result;
} else {
phase = AsyncComplete(result);
}
}
// ignore: avoid_catches_without_on_clauses
catch (e, s) {
phase = AsyncError(error: e, stackTrace: s);
}
if (!_isDisposed) {
value = phase.convert((_) => data);
if (!phase.isWaiting) {
if (phase case AsyncError(:final error, :final stackTrace)) {
onError?.call(error, stackTrace);
} else {
onComplete?.call(data);
}
}
}
return value;
}