๐ฏ Honeycomb
Concise, type-safe, codegen-free state management library for Flutter.
Honeycomb provides clear separation between State and Effect semantics, automatic dependency tracking, and a powerful Scope/Override mechanism.
โจ Features
- ๐ฏ Context-Free Usage โ Access state in pure Dart logic (Services/Repositories) via Global Container.
- โก Auto Dependency Tracking โ Computed automatically tracks dependencies from
watch. - ๐ก State vs Effect โ Clearly distinguish between replayable state and one-time events.
- ๐ญ Scope/Override โ Flexible dependency injection and local overrides.
- ๐ No Codegen โ Pure Dart, no build_runner required.
- ๐ Type Safe โ Full generic support.
- ๐งช Easy to Test โ Decouple state logic from UI for easy testing.
๐ฆ Installation
dependencies:
honeycomb: ^1.0.0
flutter pub get
๐ Quick Start
1. Define State
import 'package:aegis_honeycomb/honeycomb.dart';
// Read-write state
final counterState = StateRef(0);
// Derived state (auto dependency tracking)
final doubledCounter = Computed((watch) => watch(counterState) * 2);
// Async state
final userProfile = Computed.async((watch) async {
final userId = watch(currentUserId);
return await api.fetchUser(userId);
});
// One-time events
final toastEffect = Effect<String>();
2. Provide Container
// You can keep a global container if you don't want to rely on BuildContext.
final appContainer = HoneycombContainer();
void main() {
runApp(
HoneycombScope(
container: appContainer,
child: MyApp(),
),
);
}
3. Use in UI
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HoneycombConsumer(
builder: (context, ref, child) {
final count = ref.watch(counterState);
final doubled = ref.watch(doubledCounter);
return Column(
children: [
Text('Count: $count'),
Text('Doubled: $doubled'),
ElevatedButton(
onPressed: () {
final container = HoneycombScope.readOf(context);
container.write(counterState, count + 1);
},
child: Text('Increment'),
),
],
);
},
);
}
}
๐ Documentation
| Document | Description |
|---|---|
| Getting Started | Learn Honeycomb from scratch |
| Core Concepts | Deep dive into design philosophy |
| API Reference | Full API documentation |
| Best Practices | Recommended usage patterns |
| Comparison | Comparison with Provider/Riverpod/Bloc |
| FAQ | Frequently Asked Questions |
๐ฏ Core Concepts at a Glance
State vs Effect
// State: Replayable, always returns the latest value
final userName = StateRef('Guest');
// Effect: One-time event, no historical storage
final showToast = Effect<String>(strategy: EffectStrategy.drop);
Dependency Tracking
final fullName = Computed((watch) {
// Automatically tracks firstName and lastName
return '${watch(firstName)} ${watch(lastName)}';
});
// fullName recalculates whenever firstName or lastName changes
Scope Override
HoneycombScope supports overriding state values in a subtree using the overrides parameter. This is extremely useful for testing (Mocking) or parameterizing child components.
How it works: When resolving an Atom, the container first checks if it's in overrides; if not, it looks up the parent container; finally, it creates a new node based on the default logic.
// Locally override state (e.g., for testing or theme switching)
HoneycombScope(
overrides: [
// Force themeState to be dark
themeState.overrideWith(ThemeData.dark()),
// Or override an async state with mock data
userProfile.overrideWith(AsyncValue.data(MockUser())),
],
child: DarkModePage(),
)
Architecture Breakdown: The Complete Lifecycle of a State Update
Honeycomb uses a Push-Pull reactive model and employs several classic design patterns. When you execute something like container.write(stateRef, 1) (state changes from 0 to 1), the following 5 phases occur:
- Trigger Update & Flyweight Pattern: When calling
write, the container looks up the correspondingStateNodeinstance for the Atom in its internal_nodesdictionary. This ensures the same definition always hits the same state node (flyweight caching). - Node Creation & Visitor Pattern: If the node doesn't exist yet, the container uses
_NodeCreatorto perform a double dispatch via the Visitor Pattern (Atom.accept(visitor)). This directly creates a newStateNodetailored for theStateRefwithout cumbersome type checks under the hood. - Push Phase (Observer): After the node's value is updated, it uses the Observer Pattern to iterate through all its dependent child nodes (
ComputeNodeor UI subscribers) and sends them amarkDirty()signal. This step only marks as dirty, but does not compute, completely resolving redundant calculations caused by Diamond Dependencies. - UI Bridge (Adapter):
HoneycombConsumer, acting as a subscriber, receives the dirty signal and triggers its ownsetState(() {})via an adapter bridge. This notifies the Flutter engine to schedule a repaint in that local scope. - Pull Phase (Lazy Evaluation): In the next frame, Flutter triggers a
build, and the UI callswatch(state)to get the new value. When a node with a dirty mark is encountered, it finally performs Lazy Evaluation (Pull) and re-collects its dependencies.
Using in Business Logic (Outside Context)
Sometimes you need to access state in Repositories, Services, or pure Dart logic.
1. Create a Global Container (e.g. in app_globals.dart)
// Global singleton container
final appContainer = HoneycombContainer();
2. Use directly in Services
class AuthService {
void logout() {
// Read state
final currentUser = appContainer.read(userState);
// Write state
appContainer.write(userState, null);
// Emit event
appContainer.emit(navigationEffect, '/login');
}
}
3. Inject into UI Tree
void main() {
runApp(
HoneycombScope(
container: appContainer, // Must inject the same instance for UI updates
child: MyApp(),
),
);
}
๐งช Testing
test('counter increments', () {
final container = HoneycombContainer();
expect(container.read(counterState), 0);
container.write(counterState, 1);
expect(container.read(counterState), 1);
expect(container.read(doubledCounter), 2);
});
๐ Comparison
| Feature | Honeycomb | Provider | Riverpod | Bloc |
|---|---|---|---|---|
| No Codegen | โ | โ | โ | โ |
| Auto Tracking | โ | โ | โ | โ |
| State/Effect Separation | โ | โ | โ | โ |
| Scope Override | โ | โ | โ | โ |
| Batch Updates | โ | โ | โ | โ |
| Learning Curve | Low | Low | Medium | High |
๐ค Claude Code Integration
Use Honeycomb with Claude Code to automatically generate idiomatic Honeycomb code.
Install the official Honeycomb skill:
claude plugin install github:AegisLabsOrg/honeycomb-skill
Once installed, just describe what you need:
/honeycomb create a shopping cart state with add item and total price
Claude will generate correct StateRef, Computed, Effect, service layer, and UI code following Honeycomb best practices.
๐ค Contributing
Contributions are welcome! Please check CONTRIBUTING.md.
๐ License
MIT License - See the LICENSE file.