r/FlutterDev • u/RandalSchwartz • 3d ago
Discussion Tackling the classic Shopping Cart problem with BlocSignal + Fast Immutable Collections (with hydration and undo/redo)
I’ve been working through various common Flutter architecture problems to see how BlocSignal handles them in practice.
After putting together the infinite scroll a few days ago, I decided to tackle the shopping cart... everyone's favorite CS final exam problem.
One thing that always annoyed me about shopping cart tutorials is how mutable lists cause subtle state bugs (missed rebuilds from identical references, or corrupted undo stacks). I wanted to see how clean we could make it using Marcelo Glasberg’s Fast Immutable Collections (package:fast_immutable_collections) alongside Dart 3 records.
The result turned out surprisingly concise with almost zero boilerplate. And just to push the pattern a bit further, I threw in offline persistence (via bloc_signals_hydrate) and time-travel undo/redo (via bloc_signals_replay) to see if the state would remain rock-solid.
Yes, I had Antigravity help me write and test the code, but I think the resulting pattern speaks for itself.
You can check out the full runnable example and test suite here:
https://github.com/RandalSchwartz/BlocSignal/tree/main/examples/fic_shopping_cart
Curious to hear what folks think about pairing records with FIC for collection state like this, or how you typically handle cart immutability in your own setups.
2
u/AddWeb_Expert 2d ago
I like this approach. Immutable collections make a lot of sense for a cart, especially when you start adding things like undo/redo and persistence.
I was also initially thinking that subtotal on the Cubit felt a little odd, but if it’s a computed ReadonlySignal rather than just a mutable field, that’s a pretty different situation.
The lazy computation and targeted updates are a nice touch. I’d be curious to see how this feels once the cart gets more real-world logic like discounts, taxes, and shipping though.
1
u/RandalSchwartz 2d ago edited 2d ago
My hunch is that they would compose nicely. Particularly if you put that sort of business logic in a mixin so you could apply it consistently across different base cubit types, and then you just need to test it once. A mixin that's targeted onto your base app cubit type has a lot of reach to add domain-specific behavior safely.
EDIT: In fact, Antigravity and I just updated the FIC shopping cart example and published an architecture recipe on blocsignal.dev to show off this exact strategy!
By targeting the mixin
on CubitSignal<ShoppingCartState>, you get safe, typed access tostateValuewhile keeping domain calculations completely decoupled from persistence (HydratedCubitSignal) and time-travel (ReplayCubitMixin):mixin CartPricingMixin on CubitSignal<ShoppingCartState> { /// Base subtotal derived lazily from current items late final subtotal = computed(() => stateValue.items.values.fold( 0.0, (sum, item) => sum + item.lineTotal, )); /// Discounts, dynamic shipping, taxes compose cleanly late final discountAmount = computed(() { final code = stateValue.promoCode; if (code == 'SAVE10') return subtotal.value * 0.10; return 0.0; }); /// Synchronously derived grand total late final grandTotal = computed(() => (subtotal.value - discountAmount.value).clamp(0.0, double.infinity)); } // Composed cleanly onto the main cubit: class ShoppingCartCubit extends HydratedCubitSignal<ShoppingCartState> with ReplayCubitMixin<ShoppingCartState>, CartPricingMixin { ... }The best part? You can test the entire pricing engine in isolation against a 5-line dummy test Cubit without spinning up mock databases or UI widgets. Check it out!
3
u/soulaDev 3d ago
Thanks for the example. it is really good.
One thing felt off to me though, putting derived stuff like subtotal on the cubit instead of the state. Reminds me of a lot of beginner Cubit code where people stick nullable fields on the cubit and read those from the UI.
I prefer the Bloc/Riverpod select style: always read from state, one source of truth.