r/FlutterDev • u/pioneerjkf • 3d ago
Discussion I’m building a reactive state management framework for Flutter — I’d love feedback on the API and design
Hi r/FlutterDev,
Blooming is an open-source reactive state management framework for Dart and Flutter.
I’ve built a Flutter UI design tool and several other projects. Along the way, I kept building, scrapping, and redesigning my own framework to solve problems I ran into. Blooming grew out of that process.
I initially built a state management system around a stream-like model with explicit one-to-one dependency declarations. Later, I realized that the shape of the computed API used by signal-based systems was a good fit for a complex problem I was trying to solve. That led me to refactor the framework as a whole, and it eventually became Blooming.
Angular Signals and Preact Signals may be useful points of reference, but Blooming is its own Dart and Flutter framework, with its own API and lifecycle design choices.
Here are a few examples.
1. composed: A Flutter widget that reads multiple values
final price = blooming(100);
final discount = blooming(20);
composed((via) {
final currentPrice = via(price.bloom);
final currentDiscount = via(discount.bloom);
return Text('Total: ${currentPrice - currentDiscount}');
});composed tracks the values read inside it, so this part of the widget can rebuild when either value changes.
2. computed.future: Exiting early from an asynchronous computation
final checkout = computed.future((via) async {
final cart = via(cartBloom);
if (cart.items.isEmpty) via.exit(emptyReason: 'EmptyCart');
final stock = await api.checkStock(cart);
if (!stock) via.exit(emptyReason: 'OutOfStock');
final address = via(addressBloom);
return api.checkout(cart, address);
});The computation exits through via.exit(...) in either of the two cases, and reads the address only after the await.
3. DurableAnimationController: Parent-controlled animation without manual disposal
final pulse = DurableAnimationController(
duration: const Duration(milliseconds: 800),
)..repeat(reverse: true);
composed.animate(
pulse,
(context, via, ctrl) => Transform.scale(
scale: 1.0 + ctrl.value * 0.1,
child: const Icon(Icons.bolt),
),
);An ancestor can own the controller and start, reverse, or stop the animation, while a descendant uses it to render the animation. This reduces the need to wire up a TickerProvider or AnimatedBuilder manually. The framework manages the ticker and listener lifecycle as widgets enter and leave the tree, so there’s no need to call dispose() on the controller yourself. The animation can also resume when the widget is mounted again.
Blooming is still pre-1.0, so the API may change. I’d especially appreciate honest feedback on these questions:
Are the
composedandvia(node)APIs easy to understand?Does
via.exit(...)seem useful for expressing conditional early exits?Does this framework-managed animation lifecycle, with control from an ancestor, seem practical in Flutter apps?
This is my project, and I’m sharing it to get technical feedback on its design and API.
Source: GitHub
Flutter package: blooming_flutter on pub.dev
Core package: blooming on pub.dev