clean_cubit_reactor 1.0.3
clean_cubit_reactor: ^1.0.3 copied to clipboard
Lightweight coordination layer for Cubit with automatic synchronization between multiple cubits using Reactor.
Clean Cubit Reactor #
Clean Cubit Reactor is a lightweight coordination layer between your Repository
and multiple Cubit
s.
It automatically synchronizes their states without direct coupling between cubits and without manually triggering updates from the UI.
π See full code and examples in this repository.
Problem #
In many real-world Flutter apps, a single screen is composed of multiple independent UI parts:
- A list of data
- A button to add or update an item
- A filter widget
- etc.
Often, these parts share the same data source but use different cubits:
LoadingCubit
β loads and displays dataUpdateCubit
β adds or updates items
The problem:
When UpdateCubit
changes the data, LoadingCubit
is unaware of the change unless the UI manually triggers a reload (e.g., via a BlocListener
).
This breaks the separation of concerns β the UI should not manage business logic.
Solution: Reactor #
Reactor
is an object that:
- Has a reference to a
Repository
- Stores a local cache of the latest data
- Broadcasts updates only to cubits subscribed to the corresponding data type
- Automatically updates all subscribed cubits when the repository data changes
Result:
- Cubits donβt know about each other
- The UI stays simple
- Synchronization happens automatically
How it works #
Example #
Reactor #
class ItemsRepoReactor extends CubitReactor<ListenersType, ItemsBaseResponse> {
Future<void> loadItems() async {
setLoading(currentData: ItemsLoadResponse(data: last<ItemsLoadResponse>()?.data));
final items = await repository.getItems();
provideDataToListeners(ItemsLoadResponse(data: items));
}
Future<void> updateItem(Item item) async {
setLoading(currentData: ItemsUpdateResponse(updateItem: item));
final updatedItems = await repository.updateItem(item);
provideDataToListeners(ItemsUpdateResponse(updateItem: item));
provideDataToListeners(ItemsLoadResponse(data: updatedItems));
}
}
Cubit listening for load responses #
class ItemsCubit extends CubitListener<ListenersType, ItemsLoadResponse, ItemsState> {
ItemsCubit(this._reactor)
: super(const ItemsInitial(), _reactor, ListenersType.loadListener);
final ItemsRepoReactor _reactor;
@override
void emitOnResponse(ItemsLoadResponse response) {
if (response.error != null) {
emit(ItemsError(response.error!));
} else {
emit(ItemsLoaded(response.data ?? []));
}
}
@override
void setLoading({required ItemsLoadResponse data}) {
emit(ItemsLoading(lastData: data.data));
}
}
See example/
for a full working demo.
Benefits #
- Single Responsibility β small cubits, simple states
- Automatic synchronization β one update β all relevant cubits are notified
- Flexible β add new operation types (load, update, delete) with minimal changes
- Local caching β instant data delivery to new subscribers
- No
rxdart
orBehaviorSubject
required
Drawbacks #
- Adds an extra layer to the architecture
- Cubits must unsubscribe in
close()
to avoid memory leaks - New developers may need time to understand the
Cubit β Reactor β Repository
chain
When to use #
- Multiple independent UI components rely on the same data
- Automatic synchronization between cubits is important
- You want to avoid passing events through the UI layer
License #
MIT License.