Side Effect Cubit

An extension to the bloc state management library which serve an additional stream for events that should be consumed only once - as usual called one-time events.

Support test package: side_effect_cubit_test

Why Side Effects?

In modern state management (like Bloc or Cubit), State represents the persistent UI data (e.g., "Loading", "Loaded with Data", "Error"). The UI rebuilds whenever the state changes.

However, some actions are one-time events that shouldn't persist or trigger a UI rebuild in the traditional sense. These are called Side Effects.

Examples include:

  • Navigating to a new screen.
  • Showing a SnackBar, Toast, or Dialog.
  • Playing a sound.
  • Logging an analytics event.

If you handle these as part of the State:

  1. Re-triggering issues: If the screen rebuilds (e.g., keyboard opens, rotation), the "Show Error" state might re-trigger the error dialog/snackbar again.
  2. Boilerplate: You often have to dispatch a "Reset" event immediately after consuming the state to prevent it from happening again.

Side Effect Cubit solves this by providing a separate stream specifically for these one-off actions, decoupling them from your persistent state.

Usage

1. Adding to existing one

For Bloc:

class FeatureBloc extends Bloc<FeatureEvent, FeatureState>
    with SideEffectBlocMixin<FeatureEvent, FeatureState, FeatureSideEffect> {
  FeatureBloc() : super(FeatureState.initial());
}

For Cubit:

class FeatureCubit extends Cubit<FeatureState>
    with SideEffectCubitMixin<FeatureState, FeatureSideEffect> {
  FeatureCubit() : super(FeatureState.initial());
}

2. Inherit

For Bloc:

class FeatureBloc extends SideEffectBloc<FeatureEvent, FeatureState, FeatureSideEffect> {
  FeatureBloc() : super(FeatureState.initial());
}

For Cubit:

class FeatureCubit extends SideEffectCubit<FeatureState, FeatureSideEffect> {
  FeatureCubit() : super(FeatureState.initial());
}

Emit side effect

class FeatureBloc extends SideEffectBloc<FeatureEvent, FeatureState, FeatureSideEffect> {
  FeatureBloc() : super(FeatureState.initial()) {        
    on<ItemClick>(
      (event, emit) {
        produceSideEffect(FeatureSideEffect.openItem(event.id));
      },
    );
  }
}
class FeatureCubit extends SideEffectCubit<FeatureState, FeatureSideEffect> {
  FeatureCubit() : super(FeatureState.initial());

  Future<void> openItem(int id) async {
    produceSideEffect(FeatureSideEffect.openItem(id));
  }
}

Listen side effect

BlocSideEffectListener<FeatureBloc, FeatureSideEffect>(
  listener: (BuildContext context, FeatureSideEffect sideEffect) {
    sideEffect.when(
      goToNextScreen: () => Navigator.of(context).pushNamed("/second_screen"),
      showPopupError: (errMsg) {
        // ....
      }
    );
  },
  child: ...
)
BlocSideEffectConsumer<FeatureBloc, FeatureState, FeatureSideEffect>(
  sideEffectListener: (BuildContext context, FeatureSideEffect sideEffect) {
    // Handle side effects here
  },
  listenWhen: (previous, current) {
    // Return true/false to determine when to call listener
    return true; 
  },
  listener: (context, state) {
    // Regular Bloc listener
  },
  buildWhen: (previous, current) => true,
  builder: (BuildContext context, FeatureState state) {
    return ...
  }
)

Libraries

side_effect_cubit