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();
Obx(() => 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

GetX-like reactive system with .obs() and Obx(). 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.


πŸ—οΈ 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.3.5

2. Initialize

void main() {
  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> {
  // Correct syntax: getter returning a lambda
  @override
  CounterController Function()? get createController => () => CounterController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => 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
Obx(() => Text('${controller.todos.length} todos'))
Obx(() => ListView.builder(
  itemCount: controller.filteredTodos.length,
  itemBuilder: (context, i) => TodoItem(controller.filteredTodos[i]),
))

What you get:

  • ⚑ Minimal rebuilds (only affected widgets)
  • 🎯 Simple API (.obs(), Obx(), 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 β†’


πŸ’‘ 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

final mutation = ZenMutation<User, UpdateArgs>(
  onMutate: (args) => userQuery.data.value = args.toUser(), // Instant UI
  onError: (err, args, old) => userQuery.data.value = old,  // 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() + Obx) 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
Obx 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
  • Obx 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 query defaults
  ZenQueryConfig.defaults = ZenQueryConfig(
    staleTime: Duration(minutes: 5),
    cacheTime: Duration(hours: 1),
  );

  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 β†’


πŸ“š 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_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
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_query_cache
query/core/zen_query_config
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
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
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