ui_actionable_bloc pub version

🎬 A mixin on Bloc that allows to perform UI actions and get results back.

Getting started

Install the package

dependencies:
  ui_actionable_bloc: ^0.0.1

Add the mixin

class LoginCubit extends Cubit<LoginCubitState> with UiActionableBlocMixin<LoginCubitState, LoginAction> {

...

}

Add a listener

/// Somewhere in the UI, under the BlocProvider of LoginCubit:
@override
Widget build(BuildContext context) {
  return BlocActionsListener<LoginCubit, LoginCubitState, LoginAction>(
    listener: (context, state, action) {
      // Handle LoginAction here!
    },
    child: ...,
  );
}

Emit actions!

Future<void> login() async {
  try {
    final user = await _loginUseCase();

    await emitUiAction(LoginAction.navigateHome(user));
    // This 👆 will trigger the `listener` callback from above.
  } catch (e, s) {
    // Handle error
  }
}

Passing the result back to the Bloc

You can pass the result of the UI action back to the Bloc. For example, you can show an OTP pop up or a new route with text input after sending an OTP:

  1. Await the result of emitUiAction:

    await _sendOtpUseCase();
    
    // Otp will land here once the action is completed in a listener!
    final otp = await emitUiAction(OtpAction.requestOtpInput(user));
    
    if (otp is! String) {
      /// User did not input the OTP.
      return;
    }
    
    await _validateOtpUseCase(otp);
    
  2. Use BlocActionsListener.completable that has a slightly different listener callback:

    @override
    Widget build(BuildContext context) {
      return BlocActionsListener<OtpCubit, OtpCubitState, OtpAction>.completable(
        listener: (context, state, action, actionCompleter) {
          final otpInput = await Navigator.of(context).push(OtpInputRoute());
    
          // This will complete the emitUiAction future.
          actionCompleter(otpInput);
        },
        child: ...,
      );
    }
    

FAQ

Why?

Sometimes we need to perform a UI action in the middle of a Bloc method. For example, get user input, like an OTP code after sending an SMS. Usually BlocListeners help us with this. We listen to a change in the state and, based on certain condition, we perform a UI action. We need several methods to do this: one for triggering an OTP, another for completing the flow after the OTP is entered.

However, it is not always convenient. What do we do with the data in the state after a UI action is performed? What if it will be accessed after a UI action is performed? Do we need to clear it beforehand or let it remain there? It is unclear because these actions depend on the same data that is used by the UI.

Although it can be resolved with one-time UI Actions.

Does it work with Cubits?

Yes, it works with both Blocs and Cubits.

How is BlocActionsListener different from BlocListeners?

BlocListeners work with Bloc.states while BlocActionsListener listens to actions emitted by emitUiAction.

Where do I put a BlocActionsListener?

BlocActionsListener can be used in the same way as regular BlocListeners are used. You can even add BlocActionsListeners to a MultiBlocListener.

What if I don't complete an emitted action?

For regular BlocActionsListener, you don't have to worry about completing an action. It emitUiAction is immediately completed with a null in this case. For BlocActionsListener.completable though, make sure you complete your action with some result. Otherwise it will be stuck.

Actions received by a BlocActionsListener will be automatically completed when this listener is disposed.

Libraries

ui_actionable_bloc
A mixin on BlocBase that allows to perform UI actions and get results back right within a bloc.