When writing your own AsyncNotifier subclasses, it’s quite common to use try/catch blocks to deal with Futures that can fail:

@riverpod
class SignOutButtonController extends _$SignOutButtonController {
  @override
  FutureOr<void> build() {
    // no-op
  }
 
  Future<void> signOut() async {
    try {
      state = const AsyncValue.loading();
      await ref.read(authRepositoryProvider).signOut();
      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 {
  final authRepository = ref.read(authRepositoryProvider);
  state = const AsyncValue.loading();
  state = await AsyncValue.guard(authRepository.signOut);
}

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!