Juice is for apps that need more than local state management: auth and session lifecycles, cached networking, routing, background work, scoped cleanup and predictable UI updates. If you only need simple screens, lighter tools are usually enough. Juice is aimed at the next tier of app complexity — and it is what Amoli runs on.
Core ideas
- Use-case driven blocs. Business logic lives in dedicated use cases, not in widget callbacks.
- Lifecycle ownership. A bloc is permanent, scoped to a feature, or leased by the widgets using it — and cleaned up when its owner goes.
- Targeted rebuilds. Widgets opt into only the state changes they care about. Measured against bloc and Riverpod in the repository’s published benchmarks: one build and zero selector calls per update.
- Status-aware state. Persistent state is kept apart from transient states like waiting, failure and canceling.
- Cross-bloc orchestration. Event subscriptions and state relays are first-class, not scattered service-locator calls.
A small example
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'},
);
}
}
The family
The core package is joined by packaged infrastructure blocs, each behind a swappable seam:
- Data and network:
juice_storage,juice_network,juice_sync,juice_realtime,juice_paging - App shell:
juice_routing,juice_auth,juice_theme,juice_i18n,juice_flags,juice_forms - Device:
juice_lifecycle,juice_connectivity,juice_permissions,juice_location,juice_media,juice_notifications,juice_power - Operations:
juice_observability,juice_analytics,juice_lint - On-device AI:
juice_llm, with an embedded llama.cpp runtime injuice_llm_llamacpp
Built for AI coding agents too
Juice is barely represented in model training data, so the repository ships its own guide for coding agents and an AI card for every package, installable as a Claude Code plugin.
dependencies:
juice: ^1.10.0