Zenify
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
- โจ Elegant Async Handling: Built-in effects system for loading, error, and success states
- ๐งช 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
- ๐งช Production-validated - Currently being tested in real-world migration
๐ 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.
Development Status
๐ง Active Development Phase
Progress: Real-world production migration in progress โ๏ธ
Zenify is currently in active development with a real-world production migration underway. While the core APIs are stable and thoroughly tested, we're continuously improving based on real production feedback.
What this means for you:
- โ Core features are production-ready - Hierarchical DI, reactive state, and effects system
- โ Comprehensive test coverage - Memory leak detection, lifecycle tests, performance benchmarks
- โ Real-world validation - Currently migrating a production Flutter app to Zenify
- โ ๏ธ API refinements possible - Minor breaking changes may occur before v1.0
- ๐ Rapid improvements - Features and optimizations added based on production usage
Perfect for: New projects, prototypes, and developers who want cutting-edge state management
Consider waiting if: You need absolute API stability for large existing codebases
Quick Start (30 seconds)
1. Install
dependencies:
zenify: ^0.6.0
2. Initialize
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ZenConfig.applyEnvironment('dev');
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();
@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
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 StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Zen.find<CounterController>();
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 |
Global Module Registration
Set up your entire app's dependency architecture at startup:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Zenify
ZenConfig.applyEnvironment('dev');
// 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
void register(ZenScope scope) {
// Global services available everywhere
Zen.put<DatabaseService>(DatabaseService(), permanent: true);
Zen.put<CacheService>(CacheService(), permanent: true);
Zen.put<LoggingService>(LoggingService(), permanent: 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 initializesonClose()
runs during disposalisInitialized
is true only after a successfulonInit()
- DI behavior:
Zen.put(instance)
:ZenService
defaults toisPermanent = true
and initializes via lifecycle managerZen.putLazy(factory)
: permanence is explicit (defaultfalse
); instance is created and initialized on firstZen.find()
Zen.putFactory(factory)
: unchanged; always creates a new instance (no permanence)Zen.find()
: auto-initializes aZenService
on first access (covers lazy/scoped resolutions)
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
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
ZenModulePage
- ** 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
ZenModulePage - Route-Based Module Management
// Automatic module lifecycle tied to navigation
class AppRoutes {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/product':
return MaterialPageRoute(
builder: (_) => ZenModulePage(
moduleBuilder: () => ProductModule(),
page: ProductDetailPage(),
scopeName: 'ProductScope',
),
);
}
}
}
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
- ZenView: Use as base class for pages with controllers (โญ recommended)
- ZenRoute: Use for route-based module and scope management (โญ navigation)
- ZenConsumer: Use for accessing any dependency efficiently with null safety
- ZenBuilder: Use for manual update control with ZenControllers
- Obx: Use for reactive state with automatic rebuilds
- ZenEffectBuilder: Use for async operations with loading/error/success states
- ZenControllerScope: Use when you need explicit lifecycle control
๐๏ธ Module Organization
- Feature Modules: Create specific modules for each major feature/route
- Core Modules: Register shared services in global/parent modules
- Hierarchy Design: Keep scope depth reasonable (max 3-4 levels)
- Dependency Checking: Always verify required dependencies exist in parent scopes
- Lifecycle Hooks: Use
onInit
andonClose
for resource management
โก Performance Optimization
- Use Effects: Leverage
ZenEffect
for async operations with built-in state management - Selective Updates: Use
ZenBuilder
with specific IDs for fine-grained updates - Lazy Loading: Use
putLazy()
for dependencies that aren't immediately needed - Computed Values: Use computed properties for derived state instead of manual calculations
- Memory Management: Dispose controllers and effects properly in
onClose
๐ก๏ธ Error Handling
- Use RxResult: For operations that can fail gracefully
- Try Methods*: Leverage try* methods for safe reactive operations
- Global Handlers: Configure global error handling for production
- Fallback Values: Provide graceful fallbacks for missing dependencies
- Logging: Log errors appropriately for debugging
๐งช Testing Strategy
- Unit Tests: Test controllers in isolation using dependency injection
- Widget Tests: Use
ZenTestScope
for component testing - Integration Tests: Test module interactions and lifecycle
- Mock Dependencies: Replace services with mocks for testing
- Memory Tests: Verify proper cleanup and disposal
๐ Code Organization
- Single Responsibility: Keep controllers focused on single responsibilities
- Feature-Based Structure: Organize by feature, not by type
- Hierarchical Scopes: Use scope hierarchy for logical dependency flow
- Clear Naming: Use descriptive names for scopes and modules
- Documentation: Document complex reactive logic and dependencies
๐ซ Common Anti-Patterns to Avoid
- DON'T mix UI logic in controllers
- DON'T create circular dependencies between services
- DON'T forget to dispose resources in
onClose
- DON'T use excessive scope nesting (>4 levels)
- 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
- Reactive Core Guide - Master reactive values, collections, and computed properties
- Effects Usage Guide - Master async operations with built-in loading/error states
- State Management Patterns - Architectural patterns and best practices
- Hierarchical Scopes Guide - Advanced dependency injection and scope management
Examples & Learning
- Counter App - Simple reactive state (5 minutes)
- Todo App - CRUD operations with effects (10 minutes)
- E-commerce App - Real-world patterns (20 minutes)
- Hierarchical Scopes Demo - Advanced dependency patterns
- Showcase App - All features demonstrated
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
- Found a bug? Open an issue
- Have an idea? Start a discussion
- Need help? Check our comprehensive guides
- Want to contribute? See CONTRIBUTING.md
License
Zenify is released under the MIT License.
๐ Ready to Get Started?
Choose your path:
- ๐ New to Zenify? โ Start with Counter Example (5 min)
- ๐๏ธ Building something real? โ See E-commerce Example (20 min)
- ๐ Migrating from GetX/Provider? โ Check Migration Guide
- ๐ข Enterprise project? โ Review Hierarchical Scopes Guide
Questions? We're here to help!
- ๐ฌ Start a Discussion
- ๐ Browse Documentation
- ๐ Report Issues
Ready to bring zen to your Flutter development? Start exploring and experience the difference! โจ
Libraries
- controllers/controllers
- controllers/zen_controller
- controllers/zen_controller_scope
- controllers/zen_route_observer
- controllers/zen_service
- core/core
- core/zen_config
- core/zen_logger
- core/zen_metrics
- core/zen_module
- core/zen_scope
- core/zen_scope_manager
- core/zen_scope_stack_tracker
- debug/debug
- debug/zen_hierarchy_debug
- debug/zen_system_stats
- 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
- main
- mixins/mixins
- mixins/zen_ticker_provider
- 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_utilities
- utils/utils
- utils/zen_scope_inspector
- widgets/rx_widgets
- widgets/widgets
- widgets/zen_builder
- widgets/zen_consumer
- widgets/zen_effect_builder
- widgets/zen_route
- widgets/zen_scope_widget
- widgets/zen_view
- workers/workers
- workers/zen_workers
- zenify