Popsiclev2.1.0
GitHub ↗
Popsicle docs · 2.1.0

Flutter UI

Render reactive state with .ui(), PopsicleWidget, PopsicleBuilder, and PopsicleConsumer.

Flutter UI

Popsicle keeps Flutter rendering as a projection of state:

UI = f(state)

Store .ui()

Use .ui() as the normal Store rendering API:

counter.ui(
  (context, state, store) {
    return FilledButton(
      onPressed: store.increment,
      child: Text('$state'),
    );
  },
);

Attach one-shot effects when the feature needs navigation, snackbars, dialogs, or similar UI work:

profile.ui(
  (context, state, store) => ProfileBody(state: state),
  effect: (context, effect) {
    // Handle an occurrence once.
  },
);

ReactiveValue .ui()

Small state projects directly to a widget:

final selectedTab = Popsicle.value(0);

selectedTab.ui(
  (index) => Text('Tab $index'),
);

PopsicleWidget

Use PopsicleWidget when one widget needs several declarations or direct Store access:

class CounterActions extends PopsicleWidget {
  const CounterActions({super.key});

  @override
  Widget build(BuildContext context, Scope scope) {
    final count = scope.use(counter);
    final store = scope.store(counter);

    return Row(
      children: [
        Text('$count'),
        IconButton(
          onPressed: store.increment,
          icon: const Icon(Icons.add),
        ),
      ],
    );
  }
}

The access rules stay explicit:

scope.get(source)    -> current value, non-reactive
scope.use(source)    -> current value + rebuild dependency
scope.store(source)  -> Store instance, non-reactive
scope.select(source, selector) -> rebuild only for the selected projection

PopsicleBuilder

Use a builder when introducing a small reactive boundary inside an ordinary widget tree:

PopsicleBuilder(
  builder: (context, scope, child) {
    final mode = scope.use(themeMode);
    return Text('$mode');
  },
);

Explicit PopsicleConsumer

.ui() is implemented on top of PopsicleConsumer. Use the explicit widget when a named widget boundary is clearer:

PopsicleConsumer<CounterStore, int>(
  source: counter,
  build: (context, state, store) {
    return Text('$state');
  },
  effect: (context, effect) {
    // Handle one-shot effect.
  },
);

Prefer .ui() for ordinary feature rendering and keep PopsicleConsumer for explicit or reusable boundaries.