juice 2.0.0 copy "juice: ^2.0.0" to clipboard
juice: ^2.0.0 copied to clipboard

Lifecycle-aware Flutter application framework built around use-case driven blocs, scoped ownership, and targeted rebuilds.

Juice Logo

Juice #

pub package License: MIT

Juice is a lifecycle-aware Flutter application framework built around use-case driven blocs, explicit ownership, and targeted rebuilds.

What It Solves #

Juice is aimed at apps that need stronger structure than simple local state tools provide:

  • long-running async work with waiting, failure, and canceling states
  • app-wide blocs vs feature-owned blocs vs widget-owned blocs
  • targeted rebuilds for performance-sensitive screens
  • cross-bloc coordination without pushing orchestration into widgets
  • a coherent ecosystem for storage, networking, auth, and routing

The framework is most compelling when you lean into ownership and lifecycle, not when you use it as a generic counter-style state wrapper.

Core Capabilities #

  • Use-case driven business logic: blocs route events into dedicated use cases
  • Per-event concurrency: EventConcurrency.sequential/droppable/restartable/concurrent on every builder controls how same-type events interleave (1.5.0; restartable — latest input wins, the superseded run fenced — 2.0.0). Required since 2.0.0: a mode is a decision, and the compiler asks for it
  • Lifecycle ownership: permanent, feature, and leased lifecycles via BlocScope
  • Targeted rebuilds: rebuild groups let widgets subscribe narrowly. Measured against bloc and Riverpod (benchmarks): 1 widget rebuilt per update with 0 selector calls, the same counts as a Riverpod provider-per-cell family (selector-based targeting runs one selector per consumer per update); per frame, groups tie Riverpod's select and family forms on a Mac and a clock-pinned iPhone and lead bloc's BlocSelector (§16–§17), and untargeted defaults cost about 2.5×
  • Status-aware streams: StreamStatus separates transient workflow state from persistent app state
  • Cross-bloc orchestration: event subscriptions, state relays, and status relays
  • Per-item async state: EntityStatuses<K> + BlocUseCase.guardEntity track which rows of a collection are in flight or failed — the item-grained companion to StreamStatus, with guaranteed cleanup (no stuck spinners) (1.6.0). See the EntityStatus guide and spec.
  • Skip duplicate emits: emitUpdate(..., skipIfSame: true) drops an emission when newState == state (logging state_emission_skipped) — a per-call opt-in for no-op refreshes; requires value equality on the state (1.7.0)
  • Typed telemetry: the framework reports through JuiceLogger.record(JuiceTelemetry) — one typed object per entry, a closed vocabulary (JuiceTelemetryKind), values live, nothing stringified until a logger prints; the reference is doc/TELEMETRY.md. Every use-case execution is a span: a start and an end entry sharing a process-unique executionId, with elapsedMicros — honest durations even when same-type events overlap, live in DevTools via juice_observability's DevtoolsJuiceLogger
  • Telemetry cost follows the consumer: a logger declares the level it keeps (LevelAwareJuiceLogger) and the framework builds nothing below it — the default logger keeps nothing in release, so an unconfigured app pays for no chatter there; JuiceLoggerConfig.minLevel is the global floor. A send is 0.66 µs with the chatter built and 0.44 µs without it, on the benchmark Mac (RESULTS §15); errors and failures stay loud
  • Against bloc and Riverpod (RESULTS §16–§17, 2.0.0, Mac and a clock-pinned iPhone 17 Pro Max): sequential dispatch per update, release default — Juice 0.73 / 0.63 µs, bloc 1.04 / 1.03, Riverpod 0.42 / 0.41; with every telemetry record built Juice ties bloc; in burst bloc leads. Riverpod's notifier is a synchronous call with no event queue, the floor for that shape. Per frame the tuned Juice and Riverpod forms tie (groups vs family 0.5% / 0.0%) and bloc's BlocSelector trails them (18% on the Mac, 10% on the phone); where the scenario hurts groups (50 of 1,000 updated) Juice leads BlocSelector by 7% and Riverpod select by 17% on the phone. Drift toolchain (3.44.4 against the 3.47.5 pin); the harness declares ties when ranges overlap

Quick Example #

class CounterState extends BlocState {
  final int count;

  const CounterState({this.count = 0});

  CounterState copyWith({int? count}) {
    return CounterState(count: count ?? this.count);
  }
}

class IncrementEvent extends EventBase {}

class IncrementUseCase extends BlocUseCase<CounterBloc, IncrementEvent> {
  @override
  Future<void> execute(IncrementEvent event) async {
    emitUpdate(
      newState: bloc.state.copyWith(count: bloc.state.count + 1),
      groupsToRebuild: {'counter'},
    );
  }
}

class CounterBloc extends JuiceBloc<CounterState> {
  CounterBloc()
      : super(
          const CounterState(),
          [
            () => UseCaseBuilder(
                  typeOfEvent: IncrementEvent,
                  useCaseGenerator: () => IncrementUseCase(),
                  concurrency: EventConcurrency.sequential,
                ),
          ],
        );
}

Installation #

dependencies:
  juice: ^2.0.0

Start with the examples that show Juice at its strongest:

  1. notes_app in packages/juice_examples: leased editor, autosave, rebuild groups, storage
  2. social_feed: fetch/caching/coalescing through juice_network
  3. dashboard: auth lifecycle + routing guards
  4. ecommerce: ecosystem composition across packages

The repository root example/ app is a framework showcase and API sampler. It is useful for browsing concepts quickly, but it is not the primary architecture reference.

Ecosystem Packages #

  • juice_storage: local persistence, TTL cache, secure storage
  • juice_network: fetch, retry, coalescing, interceptor pipeline
  • juice_routing: declarative routing and guard pipeline
  • juice_auth: provider-agnostic session lifecycle

Release Notes #

This release tightens package hygiene, treats packages/juice as the single source of truth for the core framework, and shifts the public story toward lifecycle-aware app architecture rather than generic state management alone.

8
likes
160
points
775
downloads

Documentation

Documentation
API reference

Publisher

verified publishernuovea.com

Weekly Downloads

Lifecycle-aware Flutter application framework built around use-case driven blocs, scoped ownership, and targeted rebuilds.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#bloc #reactive #clean-architecture #state-management

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, logger, matcher, meta, rxdart, test_api

More

Packages that depend on juice