Skip to main content

flutter_zenith 0.1.0 copy "flutter_zenith: ^0.1.0" to clipboard
flutter_zenith: ^0.1.0 copied to clipboard

A lightweight container-scoped Flutter state management and DI library.

flutter_zenith #

Flutter Dart License

A high-performance, container-scoped state management and dependency injection engine for Flutter.

flutter_zenith merges Container-Based Scoped Memory Isolation (like Riverpod) with Surgical Graph Reactivity (like Signals) and First-Class Async Lifecycle Guards (like BLoC) into a single, zero-dependency engine.

🌟 Key Features #

  • Surgical Rendering: Widgets subscribe directly to atomic property nodes (ZenithNode<T>). Mutating a node re-renders only the listening ZenithBuilderβ€”parent and sibling widgets remain completely idle.
  • Scoped Memory Isolation: State resides inside a ZenithContainer bound to the widget tree via ZenithScope. Swapping or unmounting a scope automatically purges all contained state from memory.
  • Weak Reference Memory Safety: Node subscriptions use WeakReference and automatic subscriber pruning to prevent memory leaks, even if a listener fails to unsubscribe.
  • Async Lifecycle Guards: Built-in AsyncValue<T> state machines (AsyncData, AsyncLoading, AsyncError) paired with ref.isMounted execution tokens eliminate asynchronous post-disposal crashes.
  • Zero Dependencies: Pure Dart/Flutter framework implementationβ€”no build_runner, code generation, or third-party packages required.

πŸ“¦ Installation & Setup #

Add flutter_zenith to your pubspec.yaml:

dependencies:
  flutter_zenith:
    path: ../flutter_zenith # or package version

Import the package in your Dart code:

import 'package:flutter_zenith/flutter_zenith.dart';


πŸš€ Quick Start Guide #

1. Create State Nodes & Controllers #

State in Zenith is encapsulated in ZenithNode<T> instances. For async workflows, use AsyncValue<T>:

class CounterController {
  // Synchronous atomic node
  final counterNode = ZenithNode<int>(0);

  // Async state node
  final userProfileNode = ZenithNode<AsyncValue<String>>(const AsyncLoading());

  void increment() {
    counterNode.set(counterNode.value + 1);
  }

  Future<void> fetchUser(ZenithRef ref) async {
    userProfileNode.set(AsyncLoading(userProfileNode.value.valueOrNull));
    try {
      final name = await Future.delayed(const Duration(seconds: 1), () => 'John Doe');
      
      // Always guard async state mutations with ref.isMounted
      if (!ref.isMounted) return; 
      
      userProfileNode.set(AsyncData(name));
    } catch (e, st) {
      if (!ref.isMounted) return;
      userProfileNode.set(AsyncError(e, st));
    }
  }
}

2. Wrap the Scope in the Widget Tree #

Bind state lifecycles to your widget hierarchy using ZenithScope and ZenithContainer:

class MyApp extends StatefulWidget {
  final String userId;
  const MyApp({super.key, required this.userId});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final ZenithContainer _container;

  @override
  void initState() {
    super.initState();
    _container = ZenithContainer();
  }

  @override
  Widget build(BuildContext context) {
    // Changing the ValueKey automatically disposes the old container and creates a clean scope
    return ZenithScope(
      key: ValueKey(widget.userId),
      container: _container,
      child: const MaterialApp(
        home: HomeScreen(),
      ),
    );
  }
}

3. Build Surgically Reactive UI #

Use ZenithBuilder to read nodes. The builder automatically registers as a listener to any ZenithNode read inside its builder execution zone:

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final container = ZenithScope.of(context);
    final controller = container.getOrCreateNode(
      'counterController', 
      (ref) => CounterController(),
    ).value;

    return Scaffold(
      appBar: AppBar(title: const Text('Zenith Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // ONLY this ZenithBuilder rebuilds when counterNode changes
            ZenithBuilder(
              builder: (context) {
                final count = controller.counterNode.value;
                return Text('Count: $count', style: const TextStyle(fontSize: 24));
              },
            ),
            const SizedBox(height: 20),
            // Pattern-matching Async State
            ZenithBuilder(
              builder: (context) {
                return controller.userProfileNode.value.when(
                  loading: (prev) => Text('Loading... (Previous: $prev)'),
                  error: (err, stack) => Text('Error: $err'),
                  data: (name) => Text('Welcome, $name!'),
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}


πŸ€– AI Agent Developer Instructions & Rules #

If you are an AI coding assistant (Cursor, Claude Code, Copilot, Devin, etc.) generating or refactoring code for a project using flutter_zenith, you must strictly adhere to the following rules:

Rule 1: Zero External State Management Dependencies #

  • Do NOT import or introduce flutter_riverpod, signals, flutter_bloc, provider, or get_it.
  • Rely exclusively on package:flutter_zenith/flutter_zenith.dart.

Rule 2: Memory & Async Safety #

  • Async Execution Guards: ALL asynchronous operations (Future, Stream, or Timer callbacks) MUST check ref.isMounted before setting or mutating any ZenithNode value.
  • Node Mutation Checks: Do NOT attempt to read or mutate a disposed ZenithNode. Always ensure node disposal is handled through ZenithContainer.dispose() or ref.onDispose().
// βœ… CORRECT AI PATTERN:
Future<void> loadData(ZenithRef ref) async {
  final result = await api.fetch();
  if (!ref.isMounted) return; // REQUIRED GUARD
  node.set(AsyncData(result));
}

// ❌ INCORRECT AI PATTERN (DO NOT GENERATE):
Future<void> loadData(ZenithRef ref) async {
  final result = await api.fetch();
  node.set(AsyncData(result)); // DANGEROUS: May execute post-disposal
}

Rule 3: Surgical Building Constraints #

  • Wrap ONLY the minimal required widget subtree in a ZenithBuilder.
  • Do NOT place ZenithBuilder at the root of large screen scaffolds unless every element on the screen depends on the read nodes.
  • To prevent unnecessary observer zone leakage, perform all ZenithNode.value reads directly inside the ZenithBuilder.builder closure.
// βœ… CORRECT (Surgical):
Widget build(BuildContext context) {
  return Column(
    children: [
      const StaticHeaderWidget(), // Will NOT rebuild
      ZenithBuilder(
        builder: (context) => Text(node.value), // ONLY this rebuilds
      ),
    ],
  );
}

// ❌ INCORRECT (Broad Rebuild):
Widget build(BuildContext context) {
  return ZenithBuilder(
    builder: (context) {
      return Column(
        children: [
          const StaticHeaderWidget(), // Rebuilds unnecessarily
          Text(node.value),
        ],
      );
    },
  );
}

Rule 4: Container & Scope Management #

  • Access the active container via ZenithScope.of(context).
  • Use ZenithContainer.getOrCreateNode(key, factory) to instantiate controllers or services.
  • Always assign a distinct ValueKey to ZenithScope when user sessions change (e.g., login/logout) to force container teardown and prevent state cross-contamination.

πŸ“‚ Package Architecture & Directory Structure #

flutter_zenith/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ flutter_zenith.dart            # Main export barrel file
β”‚   └── core/
β”‚       └── zenith/
β”‚           β”œβ”€β”€ async_value.dart       # AsyncValue state machine (Data, Loading, Error)
β”‚           β”œβ”€β”€ zenith_node.dart        # Atomic reactive nodes with WeakRef tracking
β”‚           β”œβ”€β”€ zenith_container.dart   # Memory DI container, ref lifecycle, & scoping
β”‚           β”œβ”€β”€ zenith_widgets.dart     # ZenithScope & surgical ZenithBuilder
β”‚           └── zenith_core.dart       # Internal framework barrel
β”œβ”€β”€ test/
β”‚   └── core/
β”‚       └── zenith/
β”‚           β”œβ”€β”€ zenith_lifecycle_test.dart        # Teardown & isMounted validation
β”‚           β”œβ”€β”€ zenith_memory_test.dart           # Weak reference & GC pruning tests
β”‚           β”œβ”€β”€ zenith_widget_test.dart           # Rebuild isolation verification
β”‚           β”œβ”€β”€ zenith_scope_test.dart            # Scope swap & disposal tests
β”‚           └── zenith_benchmark_widget_test.dart # High-load stress & performance tests
└── example/                            # Runnable demo app


πŸ§ͺ Running Tests & Benchmarks #

Run the complete framework test suite:

flutter test

Run static code analysis:

flutter analyze

Run the runnable example application:

cd example
flutter pub get
flutter run


πŸ“œ License #

Distributed under the MIT License. See LICENSE for more information.

0
likes
0
points
335
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight container-scoped Flutter state management and DI library.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_zenith