Search code examples
flutterriverpod

How to throw error inside riverpod future provider and catch it on error flutter


final loginProvider =
    FutureProvider.family<bool, LoginParam>((ref, param) async {
  if (param.sgId == '' || param.password == '') {
    return false;
  }
  final http.Response response =
      await APIClient().login(param.sgId, param.password);
  if (response.statusCode == 200) {
    await APIClient().saveTokens(response);
    UserDefaultEntity entity =
        await ref.watch(userDefaultsProvider(param.sgId).future);
    //ref.state = AsyncValue.data(true);
    return true;
  } else {
    throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
  }
});
  void login(String userName, String password) async {
    state = AsyncValue.loading();
    AsyncValue<bool> result;
    try {
      result = await ref.refresh(loginProvider(LoginParam(userName, password)));
      state = result;
    } catch (e) {
      state = AsyncError(e);
    }
  }

I'm trying to throw an custom exception inside riverpod future provider and catch the exception in other state notifier classes, but the catch block is not triggered. Is there any other way to handle exceptions that future provider throw.


Solution

  • First of all, you won't have to manually catch errors inside a FutureProvider, it will do that for you. Refer this example.

    Generally, the operations that happen after certain "user interaction" like a button click (in this case, login operation), are not meant to be written in FutureProvider. Scenarios where you'd be using FutureProvider are as follows:

    • Fetching some data over HTTP/HTTPS.
    • Performing operations like reading a file or a local database.

    So your use case of login can be achieved using a StateNotifier.

    // auth_provider.dart
    
    import 'package:hooks_riverpod/hooks_riverpod.dart';
    
    // Always prefer some strongly typed object to 
    // know current status of authentication.
    enum AuthState {
      unauthenticated,
      authenticated,
      authenticating,
      failed,
    }
    
    // StateNotifier is recommended to encapsulate all your business
    // logic into a single class and use it from there.
    class AuthStateNotifier extends StateNotifier<AuthState> {
      // Initialize with the default state of "unauthenticated".
      const AuthStateNotifier() : super(AuthState.unauthenticated);
    
      Future<void> login(LoginParam params) async {
        if (param.sgId.isEmpty || param.password.isEmpty) {
          state = AuthState.failed;
          return;
        }
    
        final http.Response response = await APIClient().login(param.sgId, param.password);
    
        if (response.statusCode == 200) {
          await APIClient().saveTokens(response);
          UserDefaultEntity entity = await ref.watch(userDefaultsProvider(param.sgId).future);
          state = AuthState.authenticated;
          return;
        } else {
          state = AuthState.failed;
          throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
        }
      }
    }
    
    // Finally, create a provider that can be consumed in the presentation layer (UI).
    final authProvider = StateNotifierProvider<AuthStateNotifier, AuthState>((ref) => const AuthStateNotifier());
    

    Then, in your UI part, usually in the onTap / onPressed event handler of button, you can use it as follows. Please note that, we have created a button widget that extends the ConsumerWidget to access the ref.

    // login.dart
    
    import 'auth_provider.dart';
    
    class LoginButton extends ConsumerWidget {
      final LoginParam params;
    
      const LoginButton({
        Key? key,
        required this.params,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        void login() {
          try {
            await ref.read(authProvider.notifier).login(params);
          } catch (e) {
            // Handle error here.
          }
        }
    
        return ElevatedButton(
          child: Text('Login'),
          // Call the handler here.
          onPressed: login,
        );
      }
    }