Zenify

pub package likes pub points license: MIT

Complete state management for Flutterβ€”hierarchical dependency injection, reactive programming, and intelligent async state. Zero boilerplate, automatic cleanup.

// Hierarchical DI with automatic cleanup
scope.put<UserService>(UserService());
final service = scope.find<UserService>()!;  // Access from child scopes

// Reactive state that just works
final count = 0.obs();
ZenObserver(() => Text('$count'))  // Auto-rebuilds

// Smart async with caching
final userQuery = ZenQuery<User>(
  queryKey: 'user:123',
  fetcher: (_) => api.getUser(123),
);  // Caching, deduplication, refetchingβ€”all handled

🎯 Why Zenify?

Building async-heavy Flutter apps? You're probably fighting:

  • πŸ’” Manual cache management - Writing the same cache logic over and over
  • πŸ”„ Duplicate API calls - Multiple widgets fetching the same data
  • πŸ—οΈ Memory leaks - Forgetting to dispose controllers and subscriptions
  • πŸ“¦ Boilerplate overload - Hundreds of lines for simple async state

Zenify solves all of this.


⚑ What Makes Zenify Different

πŸ—οΈ Hierarchical Scoped Architecture

Riverpod-inspired scoping with automatic cleanup. Dependencies flow naturally from parent to child, and scopes dispose themselves automatically when no longer needed. Simple API: Zen.put(), Zen.find(), Zen.delete().

🎯 Zero Boilerplate Reactivity

Reactive system with .obs() and ZenObserver() (or Obx() for GetX users). Write less, accomplish more, keep your code clean. Built on Flutter's ValueNotifier for optimal performance.

πŸ”₯ React Query Style

A native-inspired implementation of TanStack Query patterns: automatic caching, smart refetching, request deduplication, and stale-while-revalidateβ€”built on top of the reactive system.

πŸ“Ά Offline-First Resilience

Don't let network issues break your app. Zenify includes Robust Persistence, an Offline Mutation Queue, and Optimistic Updates out of the box with minimal configuration.


πŸ—οΈ Understanding Scopes (The Foundation)

Zenify organizes dependencies into three hierarchical levels with automatic lifecycle management:

The Three Scope Levels

🌍 RootScope (Global - App Lifetime)

  • Services like AuthService, CartService, ThemeService
  • Lives for entire app session
  • Access anywhere via Zen.find

πŸ“¦ Module Scope (Feature - Feature Lifetime)

  • Controllers shared across feature pages
  • Auto-dispose when leaving feature
  • Example: HR feature with CompanyController β†’ DepartmentController β†’ EmployeeController

πŸ“„ Page Scope (Page - Page Lifetime)

  • Page-specific controllers
  • Auto-dispose when page pops
  • Example: LoginController, ProfileFormController

When to Use What

Scope Use For Example Lifetime
RootScope Needed across entire app Zen.find<T>() App session
Module Scope Needed across a feature Module registration Feature navigation
Page Scope Needed on one page createController Single page

The scope hierarchy automatically manages lifecycle - when you exit a feature, all its controllers clean up automatically. No memory leaks, no manual disposal.

Learn more about hierarchical scopes β†’


πŸš€ Quick Start (30 seconds)

1. Install

dependencies:
  zenify: ^1.7.0

2. Initialize

void main() async {
  await Zen.init();
  runApp(MyApp());
}

3. Create a Controller

class CounterController extends ZenController {
  final count = 0.obs();
  void increment() => count.value++;
}

4. Build UI

class CounterPage extends ZenView<CounterController> {
  @override
  CounterController Function()? get createController => () => CounterController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ZenObserver(() => Text('Count: ${controller.count.value}')),
            ElevatedButton(
              onPressed: controller.increment,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

That's it! Fully reactive with automatic cleanup. No manual disposal, no memory leaks.

Note: createController is optional! If your controller is already registered in a module or globally, you can omit it and ZenView will find the controller automatically.

See complete example β†’


πŸ”₯ Core Features

1. Hierarchical DI with Auto-Cleanup

Organize dependencies naturally with feature-based modules and parent-child scopes. When you navigate away, everything cleans up automatically.

// App-level services (persistent)
class AppModule extends ZenModule {
  @override
  void register(ZenScope scope) {
    scope.put<AuthService>(AuthService(), isPermanent: true);
    scope.put<DatabaseService>(DatabaseService(), isPermanent: true);
  }
}

// Feature-level controllers (auto-disposed)
class UserModule extends ZenModule {
  @override
  void register(ZenScope scope) {
    // Access parent services via Zen.find()
    final db = scope.find<DatabaseService>()!;

    // Register feature-specific dependencies
    scope.putLazy<UserRepository>(() => UserRepository(db));
    scope.putLazy<UserController>(() => UserController());
  }
}

// Use with any router - it's just a widget!
ZenRoute(
  moduleBuilder: () => UserModule(),
  page: UserPage(),
  scopeName: 'UserScope',
)

Core API:

  • Zen.put<T>() - Register dependencies
  • Zen.find<T>() - Retrieve dependencies
  • Zen.delete<T>() - Remove dependencies

What you get:

  • πŸ—οΈ Natural dependency flow (parent β†’ child)
  • πŸ”„ Automatic disposal (no memory leaks)
  • πŸ“¦ Clean module organization
  • πŸ§ͺ Easy testing (swap modules)

Works with: GoRouter, AutoRoute, Navigator 2.0, any router you like.

See Hierarchical Scopes Guide β†’

2. Zero-Boilerplate Reactivity

GetX-inspired reactive system built on Flutter's ValueNotifier. Simple, fast, no magic.

class TodoController extends ZenController {
  // Reactive primitives
  final todos = <Todo>[].obs();
  final filter = Filter.all.obs();

  // Computed values (auto-update)
  List<Todo> get filteredTodos {
    switch (filter.value) {
      case Filter.active: return todos.where((t) => !t.done).toList();
      case Filter.completed: return todos.where((t) => t.done).toList();
      default: return todos.toList();
    }
  }

  // Actions
  void addTodo(String title) => todos.add(Todo(title));
  void toggleTodo(Todo todo) => todo.done = !todo.done;
}

// In UI - automatic rebuilds
ZenObserver(() => Text('${controller.todos.length} todos'))
ZenObserver(() => ListView.builder(
  itemCount: controller.filteredTodos.length,
  itemBuilder: (context, i) => TodoItem(controller.filteredTodos[i]),
))

What you get:

  • ⚑ Minimal rebuilds (only affected widgets)
  • 🎯 Simple API (.obs(), ZenObserver(), done)
  • πŸ”’ Type-safe (compile-time checks)
  • 🏎️ Zero overhead (built on ValueNotifier)

See Reactive Core Guide β†’

3. Smart Async State (ZenQuery)

React Query patterns built on the reactive system.

// Define once
final userQuery = ZenQuery<User>(
  queryKey: 'user:123',
  fetcher: (_) => api.getUser(123),
  config: ZenQueryConfig(
    staleTime: Duration(minutes: 5),
    cacheTime: Duration(hours: 1),
  ),
);

// Use anywhere - automatic caching, deduplication, refetching
ZenQueryBuilder<User>(
  query: userQuery,
  builder: (context, user) => UserProfile(user),
  loading: () => CircularProgressIndicator(),
  error: (error, retry) => ErrorView(error, onRetry: retry),
);

What you get for free:

  • βœ… Automatic caching with configurable staleness
  • βœ… Smart deduplication (same key = one request)
  • βœ… Background refetch on focus/reconnect
  • βœ… Stale-while-revalidate (show cached, fetch fresh)
  • βœ… Request cancellation (no wasted bandwidth)
  • βœ… Optimistic updates with rollback
  • βœ… Infinite scroll pagination
  • βœ… Real-time streams support

Perfect for: REST APIs, GraphQL, Firebase, any async data source.

See ZenQuery Guide β†’

4. Offline Synchronization Engine (v1.6.0)

Turn your app into an offline-capable powerhouse with minimal configuration.

// Auto-persist data to disk
final postsQuery = ZenQuery<List<Post>>(
  queryKey: 'posts',
  fetcher: (_) => api.getPosts(),
  config: ZenQueryConfig(
    persist: true, 
    networkMode: NetworkMode.offlineFirst,
  ),
);

// Queue mutations when offline
final createPost = ZenMutation<Post, Post>(
  mutationKey: 'create_post', // Enables queuing
  mutationFn: (post) => api.createPost(post),
);

Key Capabilities:

  • πŸ’Ύ Storage Agnostic: Works with Hive, SharedPreferences, SQLite, etc.
  • πŸ”„ Mutation Queue: Actions are queued and auto-replayed when online.
  • ⚑ Optimistic Updates: Update UI immediately, sync later.
  • 🌐 Network Modes: Control exactly how queries behave offline.

See Offline Guide β†’


πŸ’‘ Common Patterns

Global Services with .to Pattern

Access services from anywhere without context or injection:

class CartService extends ZenService {
  static CartService get to => Zen.find<CartService>();

  final items = <CartItem>[].obs();

  void addToCart(Product product) {
    items.add(CartItem.fromProduct(product));
  }

  @override
  void onClose() {
    // Cleanup happens automatically
    super.onClose();
  }
}

// Register once
void main() {
  Zen.init();
  Zen.put<CartService>(CartService(), isPermanent: true);
  runApp(MyApp());
}

// Use anywhere - widgets, controllers, helpers
CartService.to.addToCart(product);

Infinite Scroll Pagination

final postsQuery = ZenInfiniteQuery<PostPage>(
  queryKey: ['posts'],
  infiniteFetcher: (cursor, token) => api.getPosts(cursor: cursor),
);

// Auto-load next page when reaching end
if (index == postsQuery.data.length - 1) postsQuery.fetchNextPage();

Optimistic Updates

Mutations provide automatic loading/error states, optimistic UI updates, offline queueing, and cache synchronization. Learn why mutations are better than direct API calls β†’

// ✨ Easy way: Use helpers (recommended)
final createPost = ZenMutation.listPut<Post>(
  queryKey: 'posts',
  mutationFn: (post) => api.createPost(post),
  onError: (err, post) => logger.error('Create failed', err), // Rollback automatic!
);

// Advanced: Manual control for complex scenarios
final mutation = ZenMutation<User, UpdateArgs>(
  onMutate: (args) => userQuery.data.value = args.toUser(),
  onError: (err, args, old) => userQuery.data.value = old, // Manual rollback
);

Real-Time Streams

final chatQuery = ZenStreamQuery<List<Message>>(
  queryKey: 'chat',
  streamFn: () => chatService.messagesStream,
);

See complete patterns with detailed examples β†’


πŸ› οΈ Advanced Features

  • Effects - Automatic loading/error/success state management (guide)
  • Computed Values - Auto-updating derived state with dependency tracking
  • Global Modules - Register app-wide dependencies at startup
  • Performance Control - Choose between reactive (.obs() + ZenObserver) or manual (update() + ZenBuilder)
  • Workers - Debounce, throttle, and interval-based reactive handlers
  • Devtools - Built-in inspector overlay for debugging scopes and queries

See detailed examples β†’


πŸŽ“ Learning Path

New to Zenify? Start here:

  1. 5 minutes: Counter Example - Basic reactivity
  2. 10 minutes: Todo Example - CRUD with effects
  3. 15 minutes: ZenQuery Guide - Async state management
  4. 20 minutes: E-commerce Example - Real-world patterns

Building something complex?


πŸ“± Widget Quick Reference

Choose the right widget for your use case:

Widget Use When Rebuilds On
ZenView Building pages with controllers Automatic lifecycle
ZenRoute Need module/scope per route Route navigation
ZenObserver Need reactive updates Reactive value changes
ZenBuilder Need manual control controller.update() call
ZenQueryBuilder Fetching API data Query state changes
ZenStreamQueryBuilder Real-time data streams Stream events
ZenEffectBuilder Async operations Effect state changes
ZenConsumer Accessing dependencies Manual (no auto-rebuild)

90% of the time, you'll use:

  • ZenView for pages
  • ZenObserver for reactive UI
  • ZenQueryBuilder for API calls

πŸ”§ Configuration

void main() {
  Zen.init();

  // Optional: Configure logging and performance tracking
  ZenConfig.configure(level: ZenLogLevel.info, performanceTracking: true);

  // Optional: Set global query defaults
  final queryClient = ZenQueryClient(
    defaultOptions: ZenQueryClientOptions(
      queries: ZenQueryConfig(
        staleTime: Duration(minutes: 5),
        cacheTime: Duration(hours: 1),
      ),
    ),
  );
  Zen.put(queryClient);

  runApp(MyApp());
}

πŸ§ͺ Testing

Built for testing from the ground up with mocking support:

void main() {
  setUp(() => Zen.testMode().clearQueryCache());
  tearDown(() => Zen.reset());

  test('counter increments', () {
    final controller = CounterController();
    controller.increment();
    expect(controller.count.value, 1);
  });

  test('mock dependencies', () {
    Zen.testMode().mock<ApiClient>(FakeApiClient());
    // Test code uses mock automatically
  });
}

See complete testing guide β†’


πŸ” Flutter DevTools Extension

NEW in v1.7.0: Zenify includes a comprehensive DevTools extension for real-time inspection and debugging.

Quick Setup

void main() async {
  await Zen.init(
    registerDevTools: true,  // Enables DevTools extension
  );
  runApp(MyApp());
}

Features

πŸ“Š 3-Tab Inspector:

  1. Scope Inspector - Visualize your entire DI hierarchy

    • Hierarchical tree view (root β†’ module β†’ page)
    • Dependency breakdown (Controllers, Services, Others)
    • Parent-child relationship visualization
    • Expand/collapse nodes for detailed inspection
  2. Query Cache Viewer - Monitor all cached queries

    • Search and filter queries by status
    • Visual indicators (⏳ loading, βœ… success, ❌ error, ⚠️ stale)
    • Real-time statistics (global vs scoped queries)
    • Actions: Refetch, Invalidate, Clear cache
    • Metadata: Timestamps, fetch count, scope association
  3. Metrics Dashboard - Live system performance

    • Scope metrics (Total/Active/Disposed)
    • Query metrics (Total/Loading/Error/Stale)
    • Dependency breakdown (Controllers vs Services)
    • Memory usage (RSS, Heap Size, Heap Used)
    • Auto-refreshes every 2 seconds

How to Use

  1. Start your Flutter app in debug mode
  2. Open Flutter DevTools (from VS Code, Android Studio, or command line)
  3. Look for the "Zenify" tab (with query_stats icon)
  4. Explore your scopes, queries, and metrics in real-time!

Benefits

  • βœ… Debug DI issues - See your scope hierarchy and dependencies visually
  • βœ… Optimize queries - Monitor cache hits, stale data, and fetch patterns
  • βœ… Detect memory leaks - Watch scope disposal and lifecycle
  • βœ… Performance insights - Real-time metrics help identify bottlenecks

Learn more about the DevTools extension β†’


πŸ“š Complete Documentation

Core Guides

Examples


πŸ™ Inspired By

Zenify stands on the shoulders of giants:

  • GetX by Jonny Borges - For intuitive reactive patterns
  • Riverpod by Remi Rousselet - For hierarchical scoping
  • React Query by Tanner Linsley - For smart async state

πŸ’¬ Community & Support


πŸ“„ License

MIT License - see LICENSE file


πŸš€ Ready to Get Started?

# Add to pubspec.yaml
flutter pub add zenify

# Try the examples
cd example/counter && flutter run

Choose your path:

Experience the zen of Flutter development. ✨

Libraries

controllers/controllers
controllers/zen_controller
controllers/zen_controller_scope
controllers/zen_route_observer
controllers/zen_service
core/core
core/zen_config
core/zen_environment
core/zen_exception
core/zen_log_level
core/zen_logger
core/zen_metrics
core/zen_module
core/zen_scope
debug/debug
debug/zen_debug
debug/zen_hierarchy_debug
debug/zen_system_stats
devtools/devtools
Development tools and debugging utilities for Zenify
devtools/inspector/widgets/debug_panel
devtools/inspector/widgets/dependency_list_view
devtools/inspector/widgets/query_cache_view
devtools/inspector/widgets/scope_tree_view
devtools/inspector/widgets/stats_view
devtools/inspector/zen_inspector_overlay
devtools/service_extensions
di/di
di/internal/zen_container
di/zen_dependency_analyzer
di/zen_di
di/zen_lifecycle
di/zen_reactive
di/zen_refs
effects/effects
effects/zen_effects
mixins/mixins
mixins/zen_ticker_provider
query/core/query_key
query/core/zen_cancel_token
query/core/zen_exceptions
query/core/zen_query_cache
query/core/zen_query_client
query/core/zen_query_config
query/core/zen_query_enums
Query-related enums and extensions for ZenQuery
query/core/zen_storage
query/extensions/zen_scope_query_extension
query/logic/zen_infinite_query
query/logic/zen_mutation
query/logic/zen_query
query/logic/zen_stream_query
query/query
Query system for advanced async state management
query/queue/zen_mutation_job
query/queue/zen_mutation_queue
reactive/async/rx_future
reactive/computed/rx_computed
reactive/core/reactive_base
reactive/core/rx_error_handling
reactive/core/rx_tracking
reactive/core/rx_value
reactive/extensions/rx_list_extensions
reactive/extensions/rx_map_extensions
reactive/extensions/rx_set_extensions
reactive/extensions/rx_type_extensions
reactive/reactive
reactive/testing/rx_testing
reactive/utils/rx_logger
reactive/utils/rx_timing
reactive/utils/rx_transformations
testing/testing
testing/zen_test_mode
testing/zen_test_utilities
utils/utils
utils/zen_scope_inspector
utils/zen_utils
widgets/builders/zen_builder
widgets/builders/zen_effect_builder
widgets/builders/zen_query_builder
widgets/builders/zen_stream_query_builder
widgets/components/rx_widgets
widgets/components/zen_route
widgets/components/zen_view
widgets/scope/zen_consumer
widgets/scope/zen_scope_widget
widgets/widgets
workers/workers
workers/zen_workers
zenify
Zenify - Modern Flutter state management