zenify 1.1.2
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 #
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.
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 initializesonClose()runs during disposalisInitializedis true only after a successfulonInit()
- DI behavior:
Zen.put(instance):ZenServicedefaults toisPermanent = trueand initializes via lifecycle managerZen.putLazy(factory): permanence is explicit; instance is created and initialized on firstfind(). UsealwaysNew: truefor 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 #
- 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
onInitandonClosefor resource management
β‘ Performance Optimization #
- Use Effects: Leverage
ZenEffectfor async operations with built-in state management - Selective Updates: Use
ZenBuilderwith 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
Zen.testMode()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
- Testing Guide - Comprehensive testing with mocks, utilities, and best practices
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
- π Building an API-driven app? β Start with ZenQuery Guide (15 min)
Questions? We're here to help!
- π¬ Start a Discussion
- π¬ Start a Discussion
- π Browse Documentation
- π Report Issues
Ready to bring zen to your Flutter development? Start exploring and experience the difference! β¨