Managing One-Time UI Events in Flutter with Cubit

Aditya Hastungkoro Hadi
3 min readJul 11, 2024

--

Baja Hitam Rx

Flutter is a popular framework for building cross-platform mobile applications. For state management, one approach is using the Cubit package, a lightweight alternative to the more complex Bloc library.

This article will explore how to handle one-time UI events with Cubit, using a single class for screen state, a state field for events, sealed classes for one-time events, and MultiBlocListener listening to one-time events.

Note: This approach is unlike SharedFlow in Kotlin where the event doesn’t hold any state, but more like workaround to have the same result with leveraging Bloc, especially on using previous and current state object.

Result

Setting Up Cubit with One-Time Events

Cubit Class

First, let’s define a `CukCubit` class that extends `Cubit<CukState>`. This class will manage the state and emit events to the UI.

class CukCubit extends Cubit<CukState> {
CukCubit() : super(CukState.initial());

void init() {}

void saySomething() {
emit(state.copyWith(event: CukEventSaySomething('Jancuk!!!')));
}
}

In this example, the `saySomething` method emits a state with an event of type `CukEventSaySomething`.

Event Sealed Classes

We define a sealed class `CukEvent` and its subclasses for different event types.

sealed class CukEvent {}

class CukEventInit extends CukEvent {}

class CukEventSaySomething extends CukEvent {
final String message;
CukEventSaySomething(this.message);
}

The `CukEventSaySomething` class holds a message that will be displayed to the user.

State Class

The `CukState` class includes a field named `event` to store the current event.

class CukState {
final CukEvent event;

CukState({
required this.event,
});

factory CukState.initial() {
return CukState(
event: CukEventInit(),
);
}

CukState copyWith({
CukEvent? event,
}) {
return CukState(
event: event ?? this.event,
);
}
}

The `copyWith` method allows creating a new state with modified values.

UI Implementation

In the UI, we use `MultiBlocListener` to listen for state changes and respond to events.

class CukPage extends StatelessWidget {
const CukPage({super.key});

@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<CukCubit, CukState>(
listenWhen: (previous, current) {
return current.event is CukEventSaySomething;
},
listener: (context, state) {
if (state.event is CukEventSaySomething) {
final event = state.event as CukEventSaySomething;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(event.message),
),
);
}
},
),
],
child: BlocBuilder<CukCubit, CukState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Cuk'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
context.read<CukCubit>().saySomething();
},
child: const Text('Say Something'),
),
),
);
},
),
);
}
}

How It Works

  1. **Cubit Initialization**: The `CukCubit` is initialized with an initial state.
  2. **Emitting Events**: When the user presses the button, `saySomething` is called, emitting a new state with a `CukEventSaySomething` event.
  3. **Listening for Events**: The `BlocListener` listens for state changes where the event is of type `CukEventSaySomething`. When detected, it displays a SnackBar with the event message.

Why Use Sealed Classes for Events?

Using sealed classes for events ensures that you have a well-defined set of possible event types. This provides several advantages:

1. Type Safety: Sealed classes allow the compiler to enforce that all possible event types are handled, reducing runtime errors and improving code reliability.

2. Exhaustive Checks: When using sealed classes in combination with pattern matching (e.g., switch statements), you can ensure that all cases are accounted for. This helps catch missing event types during compile-time rather than at runtime.

3. Readability and Maintainability: Sealed classes make the codebase easier to read and maintain by clearly defining the different events that can occur. This makes it easier for developers to understand and modify the code in the future.

Conclusion

Using Cubit for state management in Flutter, combined with sealed classes for one-time events, provides a clean and maintainable approach to handling UI events. This method ensures that events are processed correctly and prevents multiple triggers of the same event. By leveraging the power of `MultiBlocListener` and the simplicity of Cubit, you can create responsive and user-friendly applications.

--

--