Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Riverpod 2.0 - How to handle api status codes with FutureOr and AutoDisposeAsyncNotifier?

Just learning about Riverpod and I'm unsure about how to handle status codes from API requests. I haven't found any good examples that handle this gracefully. I'm working on a simplified login test to work this out:

final LoginAsyncProvider = AsyncNotifierProvider.autoDispose<LoginProvider, User>(() {
  return LoginProvider();
});

class LoginProvider extends AutoDisposeAsyncNotifier<User> {

  FutureOr<User> login({required String userName, required String password}) async {
    final response = await http.post(Uri.https(Api.mainDomain, Api.login));
    final json = jsonDecode(response.body) as Map<String, dynamic>;
    User _user = User.fromJson(json);
    state = AsyncData(_user);
    return _user;
  }

  @override
  FutureOr<User> build() async {
    return login(userName: "someUser", password: "somePass");  }
}

I want to use this in the UI in a switch statement:

...
    child: switch (userAsyncValue) { 
      AsyncData(:final value) => Text(text: userAsyncValue.userName),
      AsyncError() => const Text('Some Error happened'),
      _ => const CircularProgressIndicator(),
    },
...

Obviously this fails on the statement final json = jsonDecode(response.body) as Map<String, dynamic>; when the response.statusCode!=200 and does not return data in response.body.

What is the proper way to handle failures from API requests?

like image 456
J. O'Ryan Avatar asked Dec 11 '25 16:12

J. O'Ryan


1 Answers

You need to distinguish AsyncNotifier methods into three categories:

  • infrastructure (build)
  • external mutators (the public API)
  • internal helpers (frequently async)

build must always return the first value for state, which is mapped from the result of build (error, data, or future) into an AsyncValue. build should never be called except by the framework.

External mutators must always be void or Future<void>, and update state, typically by setting it first to AsyncLoading() and then using AsyncValue.guard to safely execute a function.

Internal helpers should never be called externally, and should not update state directly. Rather, they should return their values to the caller, which also should be internal. (They might also throw.) Hence, these are often made library-private, and marked @protected.

In your specific example, your login should return Future<void>, and call an internal _login function to do the heavy lifting, which either returns the new User object or throws. Your build can also call _login with an initial value for the fields, and because it's inside build, futures and exceptions will be handled properly.

Hope this helps! (And I should publish an article on this...)

like image 165
Randal Schwartz Avatar answered Dec 14 '25 05:12

Randal Schwartz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!