Zenify
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.
πΆ 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.6.0
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> {
@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:
createControlleris optional! If your controller is already registered in a module or globally, you can omit it and ZenView will find the controller automatically.
π₯ 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 dependenciesZen.find<T>()- Retrieve dependenciesZen.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)
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.
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.
π‘ 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
π Learning Path
New to Zenify? Start here:
- 5 minutes: Counter Example - Basic reactivity
- 10 minutes: Todo Example - CRUD with effects
- 15 minutes: ZenQuery Guide - Async state management
- 20 minutes: E-commerce Example - Real-world patterns
Building something complex?
- Hierarchical Scopes Guide - Advanced DI patterns
- State Management Patterns - Architectural patterns
- Testing Guide - Unit, widget, and integration tests
π± 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:
ZenViewfor pagesObxfor reactive UIZenQueryBuilderfor 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 β
π Complete Documentation
Core Guides
- Reactive Core Guide - Reactive values, collections, computed properties
- ZenQuery Guide - Async state, caching, mutations
- Offline-First Guide - Persistence & Synchronization
- Effects Guide - Async operations with state management
- Hierarchical Scopes - Advanced DI patterns
- State Management Patterns - Architectural patterns
- Testing Guide - Testing strategies and utilities
Examples
- Counter - Simple reactive state
- Todo App - CRUD operations
- E-commerce - Real-world patterns
- Hierarchical Scopes Demo - Advanced DI
- ZenQuery Demo - Async state management
- Offline Demo - Persistence & Queues (New!)
- Showcase - All features
π 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
- π Found a bug? Report it
- π‘ Have an idea? Discuss it
- π Need help? Check our documentation
π 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:
- π New to Zenify? β 5-minute Counter Tutorial
- π₯ Want async superpowers? β ZenQuery Guide
- πΆ Need offline support? β Offline Guide
- ποΈ Building something complex? β Hierarchical Scopes Guide
- π§ͺ Setting up tests? β Testing Guide
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_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