zenify 1.1.2 copy "zenify: ^1.1.2" to clipboard
zenify: ^1.1.2 copied to clipboard

Modern Flutter state management with hierarchical DI, automatic memory leak prevention, and reactive programming. Bring zen to your development.

Zenify #

pub package likes pub points license: MIT

A modern state management library for Flutter that brings true "zen" to your development experience. Clean, intuitive, and powerful.

Why Zenify? #

Stop fighting with state management. Zenify offers an elegant solution that keeps your codebase clean and your mind at peace:

  • πŸš€ Less Boilerplate: Write less code while accomplishing more
  • πŸ—οΈ Module System: Organize dependencies into clean, reusable modules
  • πŸ”— Natural Hierarchy: Nested scopes that automatically inherit from parents
  • ⚑ Flexible Reactivity: Choose between automatic UI updates or manual control
  • πŸ”’ Strong Type Safety: Catch errors at compile-time with enhanced type constraints
  • πŸ”₯ ZenQuery System: React Query-inspired async state management with intelligent caching, automatic deduplication, background refetching, and scope-aware lifecycle management
  • ✨ Elegant Async Handling: Built-in effects system for loading, error, and success states
  • πŸ” Production-Safe Logging: Type-safe, environment-based configuration with granular log levels
  • πŸ§ͺ Testing Ready: Comprehensive testing utilities out of the box

What Makes Zenify Different? #

Zenify builds on the shoulders of giants, taking inspiration from excellent libraries like GetX, Provider, and Riverpod. Our focus is on bringing hierarchical dependency injection and automatic lifecycle management to Flutter state management.

Zenify's unique strengths:

  • πŸ—οΈ Native hierarchical scopes - Dependencies flow naturally from parent to child
  • πŸ”„ Automatic cleanup - No manual disposal needed, prevents memory leaks
  • ✨ Built-in async effects - Loading/error states handled automatically
  • 🎯 Simplified API - One obvious way to do each task

πŸ”„ Familiar Patterns, Enhanced Features #

If you're familiar with GetX, you'll feel right at home! Zenify draws inspiration from Jonny Borges' excellent work, preserving the reactive patterns, keeping the API very similar, while adding enhanced capabilities for complex applications.

We've also incorporated proven concepts from Riverpod's hierarchical scoping and Provider's context-based inheritance to create a comprehensive solution.

Quick Start (30 seconds) #

1. Install #

dependencies:
  zenify: ^1.1.2

2. Initialize #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Zen.init();
  
  // Type-safe configuration (recommended) ✨
  if (kReleaseMode) {
    ZenConfig.applyEnvironment(ZenEnvironment.production);
  } else {
    ZenConfig.applyEnvironment(ZenEnvironment.development);
  }
  
  //OR
  
  // Fine-grained control
  ZenConfig.configure(
    level: ZenLogLevel.info,
    performanceTracking: true,
  );
  
  runApp(const MyApp());
}

3. Create Your First Controller #

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

4. (Optional) Register a Service #

class LoggingService extends ZenService {
  @override
  void onInit() {/* setup sinks, files, etc. */}

  @override
  void onClose() {/* flush and close */}
}

// Permanent by default when using Zen.put
Zen.put<LoggingService>(LoggingService());

5. Use in Your Page #

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          children: [
            Obx(() => Text('Count: ${controller.count.value}')),
            ElevatedButton(
              onPressed: controller.increment,
              child: const Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

That's it! You have a fully reactive counter with automatic cleanup and type safety.

⚑ Performance Highlights #

  • Minimal Rebuilds: Only affected widgets update, not entire subtrees
  • Memory Efficient: Automatic scope cleanup prevents leaks and dangling references
  • Zero Overhead: Built on Flutter's optimized ValueNotifier foundation
  • Smart Disposal: Intelligent lifecycle management with hierarchical cleanup
  • Production Tested: Real-world app migration validates performance at scale

See Performance Guide for detailed benchmarks

Production-Ready Reactive System #

Beyond basic reactive state - Zenify includes a comprehensive reactive system designed for production applications:

RxFuture - Reactive Async Operations #

class DataController extends ZenController {
  late final RxFuture<List<User>> usersFuture;

  @override
  void onInit() {
    super.onInit();
    usersFuture = RxFuture.fromFactory(() => userService.getUsers());
  }

  void refreshData() => usersFuture.refresh(); // Automatic loading states
}

// In UI - automatic state management
Obx(() {
  if (controller.usersFuture.isLoading) return CircularProgressIndicator();
  if (controller.usersFuture.hasError) return ErrorWidget(controller.usersFuture.errorMessage);
  if (controller.usersFuture.hasData) return UserList(users: controller.usersFuture.data!);
  return SizedBox.shrink();
})

RxComputed - Smart Dependency Tracking #

class ShoppingController extends ZenController {
  final cartItems = <CartItem>[].obs();
  final taxRate = 0.08.obs();

  late final RxComputed<double> subtotal;
  late final RxComputed<double> tax;
  late final RxComputed<double> total;

  @override
  void onInit() {
    super.onInit();
    
    // These automatically update when dependencies change
    subtotal = computed(() => 
      cartItems.fold(0.0, (sum, item) => sum + (item.price * item.quantity))
    );
    tax = computed(() => subtotal.value * taxRate.value);
    total = computed(() => subtotal.value + tax.value);
  }

  void addItem(CartItem item) {
    cartItems.add(item); // All computed values automatically update!
  }
}

// In UI - automatic updates
Obx(() => Text('Subtotal: \$${controller.subtotal.value.toStringAsFixed(2)}'))
Obx(() => Text('Tax: \$${controller.tax.value.toStringAsFixed(2)}'))
Obx(() => Text('Total: \$${controller.total.value.toStringAsFixed(2)}'))

RxResult - Production Error Handling #

class UserController extends ZenController {
  Future<void> saveUser(User user) async {
    final result = await RxResult.tryExecuteAsync(
      () => userService.saveUser(user),
      'save user'
    );
    
    result.onSuccess((savedUser) {
      users.add(savedUser);
      showSuccess('User saved successfully');
    });
    
    result.onFailure((error) {
      showError('Failed to save user: ${error.message}');
    });
  }

  // Safe list operations with error handling
  void updateUserSafely(int index, User newUser) {
    final result = users.trySetAt(index, newUser);
    if (result.isFailure) showError('Invalid index: $index');
  }
}

Advanced Reactive Patterns #

class AdvancedController extends ZenController {
  final searchQuery = ''.obs();
  final products = <Product>[].obs();
  final isLoading = false.obs();

  @override
  void onInit() {
    super.onInit();

    // Debounced search with error handling
    searchQuery.debounce(Duration(milliseconds: 500), (query) async {
      if (query.isEmpty) return products.clear();

      isLoading.value = true;
      final result = await RxResult.tryExecuteAsync(
        () => productService.search(query)
      );
      
      result.onSuccess((results) => products.assignAll(results));
      result.onFailure((error) => showError('Search failed: ${error.message}'));
      
      isLoading.value = false;
    });
  }
}

Benefits:

  • ✨ Comprehensive error handling with graceful degradation
  • 🎯 Smart dependency tracking with automatic cleanup
  • πŸ”’ Type-safe async operations with built-in loading states
  • 🏭 Production-validated with real-world error scenarios

Handle Async Operations with Effects #

class UserController extends ZenController {
  late final userEffect = createEffect<User>(name: 'user');
  
  Future<void> loadUser() async {
    await userEffect.run(() => api.getUser());
  }
}

// In your UI - automatic loading states
ZenEffectBuilder<User>(
  effect: controller.userEffect,
  onLoading: () => const CircularProgressIndicator(),
  onSuccess: (user) => UserProfile(user),
  onError: (error) => ErrorMessage(error),
)

Benefits:

  • ✨ Automatic state management - Loading, success, error handled for you
  • πŸ”„ Retry logic - Built-in error recovery and retry mechanisms
  • πŸ”’ Type safety - Full compile-time guarantees for async operations
  • πŸ§ͺ Testing friendly - Easy to mock and test different states

πŸ”₯ ZenQuery - Smart Async State Management #

React Query-like functionality for Flutter with automatic caching, deduplication, and background refetching.

// Create query
final userQuery = ZenQuery<User>(
  queryKey: 'user:123',
  fetcher: () => api.getUser(123),
);

// Use in widget
ZenQueryBuilder<User>(
  query: userQuery,
  builder: (context, user) => Text(user.name),
  loading: () => CircularProgressIndicator(),
  error: (error, retry) => ErrorWidget(error: error, onRetry: retry),
);

Features:

  • βœ… Automatic caching - No more manual cache management
  • βœ… Smart deduplication - Multiple requests = single API call
  • βœ… Background refetch - Keep data fresh automatically
  • βœ… Pagination support - Built-in patterns for paginated data
  • βœ… Optimistic updates - Instant UI with error rollback
  • βœ… Retry logic - Exponential backoff built-in
  • βœ… SWR pattern - Show cached data while fetching fresh
  • βœ… Scoped lifecycle - Automatic cleanup with modules

Perfect for: REST APIs, GraphQL queries, pagination, infinite scroll, and real-time data feeds.

See ZenQuery Guide

Flexible Widget System #

Choose the right widget for your needs:

ZenConsumer - Efficient Dependency Access #

// Access services efficiently
ZenConsumer<CartService>(
  builder: (cartService) => cartService != null
    ? CartIcon(itemCount: cartService.itemCount)
    : const EmptyCartIcon(),
)

// Access optional dependencies gracefully
ZenConsumer<AuthService>(
  tag: 'premium',
  builder: (authService) => authService?.isAuthenticated.value == true
    ? const PremiumFeatures()
    : const UpgradePrompt(),
)

ZenBuilder - Performance Control #

class PerformanceOptimizedView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Only rebuilds when controller.update(['header']) is called
        ZenBuilder<DashboardController>(
          id: 'header',
          builder: (context, controller) => AppBar(
            title: Text(controller.title),
            actions: [
              IconButton(
                icon: Icon(controller.settingsIcon),
                onPressed: controller.openSettings,
              ),
            ],
          ),
        ),

        // Only rebuilds when controller.update(['content']) is called
        ZenBuilder<DashboardController>(
          id: 'content',
          builder: (context, controller) => Expanded(
            child: ListView.builder(
              itemCount: controller.items.length,
              itemBuilder: (context, index) => ItemWidget(
                item: controller.items[index]
              ),
            ),
          ),
        ),
      ],
    );
  }
}

Obx - Reactive Updates #

class ReactiveWidget extends ZenView<CounterController> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Automatically rebuilds when counter.value changes
        Obx(() => Text('Count: ${controller.counter.value}')),

        // Multiple reactive values
        Obx(() => AnimatedContainer(
          duration: Duration(milliseconds: 300),
          color: controller.isActive.value ? Colors.green : Colors.red,
          child: Text('Status: ${controller.status.value}'),
        )),
      ],
    );
  }
}

Widget Comparison #

Widget Purpose Rebuild Trigger Best For
ZenView Page base class Automatic lifecycle Full pages with controllers
ZenRoute Route navigation Route lifecycle Module/scope management
ZenConsumer Dependency access Manual Optional service access
ZenBuilder Manual updates controller.update() Performance optimization
Obx Reactive updates Reactive value changes Simple reactive widgets
ZenEffectBuilder Async operations Effect state changes Loading/Error/Success states
ZenControllerScope Custom lifecycle Manual scope control Explicit lifecycle management
ZenQueryBuilder Query operations Query state changes API calls with caching

Global Module Registration #

Set up your entire app's dependency architecture at startup:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize Zenify
  Zen.init();
  ZenConfig.applyEnvironment(ZenEnvironment.development);
  
  // Register global modules
  await Zen.registerModules([
    // Core infrastructure
    CoreModule(),        // Database, storage, logging
    NetworkModule(),     // API clients, connectivity
    AuthModule(),        // Authentication, user management
  ]);
  
  runApp(const MyApp());
}

class CoreModule extends ZenModule {
  @override
  String get name => 'Core';

  @override
  void register(ZenScope scope) {
    // Global services available everywhere
    scope.put<DatabaseService>(DatabaseService(), isPermanent: true);
    scope.put<CacheService>(CacheService(), isPermanent: true);
    scope.put<LoggingService>(LoggingService(), isPermanent: true);
  }
}

// Access anywhere in your app
class AnyController extends ZenController {
  // These are automatically available from global registration
  final database = Zen.find<DatabaseService>();
  final cache = Zen.find<CacheService>();
  final logger = Zen.find<LoggingService>();
}

Benefits:

  • πŸ—‚οΈ Centralized setup - Configure your entire app architecture in one place
  • πŸ”₯ Hot reload friendly - Services persist across development iterations
  • πŸ§ͺ Testing support - Easy to swap modules for testing
  • πŸ“¦ Feature isolation - Keep related dependencies grouped together

Services (ZenService) #

Long-lived app-wide services (e.g., auth, logging, cache) with safe lifecycle.

  • Lifecycle:
    • onInit() runs when the service first initializes
    • onClose() runs during disposal
    • isInitialized is true only after a successful onInit()
  • DI behavior:
    • Zen.put(instance): ZenService defaults to isPermanent = true and initializes via lifecycle manager
    • Zen.putLazy(factory): permanence is explicit; instance is created and initialized on first find(). Use alwaysNew: true for factory pattern (new instance each call)

Example:

class AuthService extends ZenService {
  late final StreamSubscription _tokenSub;

  @override
  void onInit() {
    _tokenSub = tokenStream.listen(_handleToken);
  }

  @override
  void onClose() {
    _tokenSub.cancel();
  }

  void _handleToken(String token) {/* ... */}
}

// Registration: permanent by default for services
Zen.put<AuthService>(AuthService());

// Lazy registration (make permanent explicitly if desired)
Zen.putLazy<AuthService>(() => AuthService(), isPermanent: true);

// Usage anywhere
final auth = Zen.find<AuthService>(); // auto-initializes if needed

Organize with Modules #

Scale your app with clean dependency organization:

// Define module with controller
class UserModule extends ZenModule {
  @override
  String get name => 'User';

  @override
  void register(ZenScope scope) {
    scope.putLazy<UserService>(() => UserService());
    scope.putLazy<UserController>(() => UserController());
  }
}

// Use in routes with automatic cleanup
ZenRoute(
  moduleBuilder: () => UserModule(),
  page: UserProfilePage(), // Page extends ZenView<UserController>
  scopeName: 'UserScope'
)

// Page doesn't need createController() - gets it from module
class UserProfilePage extends ZenView<UserController> {
  // No createController override needed!
  // ZenView automatically finds UserController from the module
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User Profile')),
      body: Obx(() => Text('User: ${controller.userName.value}')),
    );
  }
}

Benefits:

  • πŸ—‚οΈ Organized Dependencies - Group related services together
  • πŸ”„ Automatic Cleanup - Modules dispose when routes change
  • πŸ—οΈ Hierarchical Inheritance - Child modules access parent services
  • πŸ§ͺ Testing Friendly - Swap modules for testing

πŸ› οΈ Advanced Features #

For complex applications:

  • πŸ—ΊοΈ Route-Based Scoping - Automatic module lifecycle with navigation using ZenRoute
  • πŸ—οΈ Hierarchical Dependency Injection - Parent-child scope inheritance with ZenScopeWidget
  • 🏷️ Tagged Dependencies - Multiple instances with smart resolution
  • πŸ“Š Performance Monitoring - Built-in metrics and leak detection
  • πŸ§ͺ Comprehensive Testing - Mocking, lifecycle, and memory leak tests
  • πŸ”„ Advanced Lifecycle Hooks - Module initialization and disposal callbacks

ZenRoute - Works with Any Router #

ZenRoute works seamlessly with any Flutter routing solution - it's just a widget!

// βœ… Works with Flutter's built-in Navigator
class AppRoutes {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/product':
        return MaterialPageRoute(
          builder: (_) => ZenRoute(
            moduleBuilder: () => ProductModule(),
            page: ProductDetailPage(),
            scopeName: 'ProductScope',
          ),
        );
    }
  }
}

// βœ… Works with GoRouter
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/product/:id',
      builder: (context, state) => ZenRoute(
        moduleBuilder: () => ProductModule(),
        page: ProductDetailPage(id: state.pathParameters['id']!),
        scopeName: 'ProductScope',
      ),
    ),
  ],
);

// βœ… Works with AutoRoute
@AutoRouterConfig()
class AppRouter extends $AppRouter {
  @override
  List<AutoRoute> get routes => [
    AutoRoute(
      page: ProductRoute.page,
      path: '/product',
    ),
  ];
}

@RoutePage()
class ProductRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ZenRoute(
      moduleBuilder: () => ProductModule(),
      page: ProductDetailPage(),
      scopeName: 'ProductScope',
    );
  }
}

// βœ… Works with any custom routing solution
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => ZenRoute(
      moduleBuilder: () => SettingsModule(),
      page: SettingsPage(),
      scopeName: 'SettingsScope',
    ),
  ),
);

Key Benefits:

  • 🎯 Router Agnostic - Works with any routing package or custom solution
  • πŸ”„ Automatic Cleanup - Modules and scopes dispose when route is popped
  • πŸ—οΈ Hierarchical - Child routes inherit from parent scopes
  • πŸ§ͺ Testable - Easy to test without full navigation stack

Pro Tip: Use ZenRoute for feature-level routes that need dependency isolation. For simple pages, ZenView alone is often sufficient!

ZenScopeWidget - Custom Scoping #

// Create scopes at any widget level
showModalBottomSheet(
  context: context,
  builder: (context) => ZenScopeWidget(
    moduleBuilder: () => FilterModule(),
    scopeName: 'FilterScope',
    child: const FilterBottomSheet(),
  ),
);

πŸ“± Best Practices #

🎯 Widget Selection #

  1. ZenView: Use as base class for pages with controllers (⭐ recommended)
  2. ZenRoute: Use for route-based module and scope management (⭐ navigation)
  3. ZenConsumer: Use for accessing any dependency efficiently with null safety
  4. ZenBuilder: Use for manual update control with ZenControllers
  5. Obx: Use for reactive state with automatic rebuilds
  6. ZenEffectBuilder: Use for async operations with loading/error/success states
  7. ZenControllerScope: Use when you need explicit lifecycle control

πŸ—οΈ Module Organization #

  1. Feature Modules: Create specific modules for each major feature/route
  2. Core Modules: Register shared services in global/parent modules
  3. Hierarchy Design: Keep scope depth reasonable (max 3-4 levels)
  4. Dependency Checking: Always verify required dependencies exist in parent scopes
  5. Lifecycle Hooks: Use onInit and onClose for resource management

⚑ Performance Optimization #

  1. Use Effects: Leverage ZenEffect for async operations with built-in state management
  2. Selective Updates: Use ZenBuilder with specific IDs for fine-grained updates
  3. Lazy Loading: Use putLazy() for dependencies that aren't immediately needed
  4. Computed Values: Use computed properties for derived state instead of manual calculations
  5. Memory Management: Dispose controllers and effects properly in onClose

πŸ›‘οΈ Error Handling #

  1. Use RxResult: For operations that can fail gracefully
  2. Try Methods*: Leverage try* methods for safe reactive operations
  3. Global Handlers: Configure global error handling for production
  4. Fallback Values: Provide graceful fallbacks for missing dependencies
  5. Logging: Log errors appropriately for debugging

πŸ§ͺ Testing Strategy #

  1. Unit Tests: Test controllers in isolation using dependency injection
  2. Widget Tests: Use Zen.testMode() for component testing
  3. Integration Tests: Test module interactions and lifecycle
  4. Mock Dependencies: Replace services with mocks for testing
  5. Memory Tests: Verify proper cleanup and disposal

πŸ“ Code Organization #

  1. Single Responsibility: Keep controllers focused on single responsibilities
  2. Feature-Based Structure: Organize by feature, not by type
  3. Hierarchical Scopes: Use scope hierarchy for logical dependency flow
  4. Clear Naming: Use descriptive names for scopes and modules
  5. Documentation: Document complex reactive logic and dependencies

🚫 Common Anti-Patterns to Avoid #

  1. DON'T mix UI logic in controllers
  2. DON'T create circular dependencies between services
  3. DON'T forget to dispose resources in onClose
  4. DON'T use excessive scope nesting (>4 levels)
  5. DON'T ignore error handling in production code

πŸ“‹ Quick Checklist #

Before releasing to production:

  • βœ… Controllers have single responsibilities
  • βœ… Async operations use effects or proper error handling
  • βœ… Resources are properly disposed in onClose()
  • βœ… Dependencies are organized in logical modules
  • βœ… Critical paths have comprehensive tests
  • βœ… Error states have user-friendly fallbacks
  • βœ… Performance-critical sections use targeted updates
  • βœ… Scope hierarchy is clean and purposeful

Explore Advanced Guides for production patterns and comprehensive examples

πŸ“š Complete Documentation #

New to Zenify? Start with the guides that match your needs:

Core Guides #

Examples & Learning #

Quick References (Coming Soon) #

  • Core Concepts - Controllers, reactive state, and UI widgets
  • API Reference - Complete method and class documentation
  • Migration Guide - Moving from other state management solutions
  • Testing Guide - Unit and widget testing with Zenify

πŸ”‘ Key Features at a Glance #

ZenView - Direct Controller Access #

class ProductPage extends ZenView<ProductController> {
  @override
  Widget build(BuildContext context) {
    // Direct access - no Zen.find() needed!
    return Text(controller.productName.value);
  }
}

Smart Effects System #

// Automatic state management for async operations
late final dataEffect = createEffect<List<Item>>(name: 'data');

await dataEffect.run(() => api.fetchData());
// Loading, success, and error states handled automatically

Hierarchical Dependency Injection #

// Parent scope provides shared services
ZenRoute(
  moduleBuilder: () => AppModule(),
  page: HomePage(),
  scopeName: 'AppScope',
)

// Child widgets automatically access parent dependencies
final authService = Zen.find<AuthService>(); // Available everywhere

Flexible Reactivity #

// Option 1: Automatic reactive updates
Obx(() => Text('Count: ${controller.count.value}'))

// Option 2: Manual control for performance
ZenBuilder<Controller>(
  id: 'specific-section',
  builder: (context, controller) => ExpensiveWidget(controller.data),
)
// Only rebuilds when controller.update(['specific-section']) is called

πŸ™ Credits #

Zenify draws inspiration from several excellent state management libraries:

  • GetX by Jonny Borges - For the intuitive reactive syntax and dependency injection approach
  • Provider by Remi Rousselet - For context-based dependency inheritance concepts
  • Riverpod by Remi Rousselet - For improved type safety and testability patterns

πŸ’¬ Community & Support #

πŸ“„ License #

Zenify is released under the MIT License.

πŸš€ Ready to Get Started? #

Choose your path:

Questions? We're here to help!

Ready to bring zen to your Flutter development? Start exploring and experience the difference! ✨

1
likes
160
points
312
downloads

Publisher

unverified uploader

Weekly Downloads

Modern Flutter state management with hierarchical DI, automatic memory leak prevention, and reactive programming. Bring zen to your development.

Repository (GitHub)
View/report issues

Topics

#state-management #hierarchical-di #dependency-injection #flutter #memory-management

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on zenify