When writing your own StateNotifier
subclasses, it's quite common to use try/catch blocks to deal with Futures that can fail:
class SignOutButtonController extends StateNotifier<AsyncValue> {
SignOutButtonController({required this.authRepository})
: super(const AsyncValue.data(null));
final AuthRepository authRepository;
Future<void> signOut() async {
try {
state = const AsyncValue.loading();
await authRepository.signOut(); // this future can fail
state = const AsyncValue.data(null);
} catch (e) {
state = AsyncValue.error(e);
}
}
}
In such cases, AsyncValue.guard
is a convenient alternative that does all the heavy-lifting for us:
Future<void> signOut() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => authRepository.signOut());
}
sponsor

Level up your Flutter app’s performance. Get full visibility into your Flutter app’s performance, including crashes, exceptions in Dart, network health, custom traces, and more. Solve your trickiest issues, optimize user journeys, and wow your end users - again and again.
Here's how this method is implemented in the the AsyncValue
class:
abstract class AsyncValue<T> {
static Future<AsyncValue<T>> guard<T>(Future<T> Function() future) async {
try {
return AsyncValue.data(await future());
} catch (err, stack) {
return AsyncValue.error(err, stackTrace: stack);
}
}
}
Learn more here: AsyncValue.guard() method.
Happy coding!