Zenify
Modern Flutter state management that combines React Query's async superpowers, Riverpod's hierarchical DI, and GetX's simplicityβall in one elegant package.
// Smart caching, auto-refetch, zero boilerplate
final userQuery = ZenQuery<User>(
queryKey: 'user:123',
fetcher: (_) => api.getUser(123),
);
// That's it. Caching, loading states, error handlingβ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
π₯ React Query Style
A native-inspired implementation of TanStack Query patterns: automatic caching, smart refetching, request deduplication, and stale-while-revalidateβseamlessly integrated with your UI.
ποΈ 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.
π― Zero Boilerplate
GetX-like reactivity with .obs() and Obx(). Write less, accomplish more, keep your code clean.
π Quick Start (30 seconds)
1. Install
dependencies:
zenify: ^1.2.2
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 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.
π₯ Core Features
1. Smart Async State (ZenQuery)
The killer feature. React Query patterns for Flutter.
// 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.
2. Hierarchical DI with Auto-Cleanup
Organize dependencies naturally with 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 services (auto-disposed)
class UserModule extends ZenModule {
@override
void register(ZenScope scope) {
// Access parent services
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',
)
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 β
3. 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)
π‘ Real-World Examples
Infinite Scroll with Pagination
final postsQuery = ZenInfiniteQuery<PostPage>(
queryKey: ['posts', 'feed'],
infiniteFetcher: (cursor, token) => api.getPosts(cursor: cursor),
getNextPageParam: (lastPage) => lastPage.nextCursor,
);
// In UI
ListView.builder(
itemCount: postsQuery.data.length,
itemBuilder: (context, index) {
if (index == postsQuery.data.length - 1) {
postsQuery.fetchNextPage(); // Load more
}
return PostCard(postsQuery.data[index]);
},
)
Mutations with Optimistic Updates
final updateUserMutation = ZenMutation<User, UpdateUserArgs>(
mutationFn: (args) => api.updateUser(args),
onMutate: (args) {
// Optimistic update
final oldUser = userQuery.data.value;
userQuery.data.value = args.toUser();
return oldUser; // Context for rollback
},
onError: (error, args, context) {
// Rollback on error
userQuery.data.value = context as User;
showError('Update failed');
},
onSettled: () {
// Refresh query
userQuery.refetch();
},
);
// Trigger
updateUserMutation.mutate(UpdateUserArgs(name: 'New Name'));
Real-Time Data Streams
final chatQuery = ZenStreamQuery<List<Message>>(
queryKey: 'chat-messages',
streamFn: () => chatService.messagesStream,
);
ZenStreamQueryBuilder<List<Message>>(
query: chatQuery,
builder: (context, messages) => ChatList(messages),
loading: () => LoadingSpinner(),
error: (error) => ErrorView(error),
);
π οΈ Advanced Features
Effects for Async Operations
Automatic state management for loading/error/success.
class UserController extends ZenController {
late final userEffect = createEffect<User>(name: 'loadUser');
Future<void> loadUser(String id) async {
await userEffect.run(() => api.getUser(id));
}
}
// In UI - automatic state handling
ZenEffectBuilder<User>(
effect: controller.userEffect,
onLoading: () => LoadingSpinner(),
onSuccess: (user) => UserProfile(user),
onError: (error) => ErrorMessage(error),
)
Computed Values with Dependency Tracking
class ShoppingController extends ZenController {
final items = <CartItem>[].obs();
final discount = 0.0.obs();
late final subtotal = computed(() =>
items.fold(0.0, (sum, item) => sum + item.price)
);
late final total = computed(() =>
subtotal.value * (1 - discount.value)
);
}
// Automatic updates when items or discount change
Obx(() => Text('Total: \$${controller.total.value}'))
Global Module Registration
Set up your entire app architecture at startup.
void main() async {
Zen.init();
// Register app-wide modules
await Zen.registerModules([
CoreModule(), // Database, logging, storage
NetworkModule(), // API clients, connectivity
AuthModule(), // Authentication
]);
runApp(MyApp());
}
Performance Control
Fine-grained rebuild control when you need it.
class DashboardController extends ZenController {
final stats = <Stat>[].obs();
final isLoading = false.obs();
void updateStats() {
// Only rebuild specific widgets
update(['stats-widget']);
}
}
// In UI
ZenBuilder<DashboardController>(
id: 'stats-widget',
builder: (context, controller) => StatsChart(controller.stats),
)
π 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
Basic Setup
void main() {
Zen.init();
// Environment-based config
if (kReleaseMode) {
ZenConfig.applyEnvironment(ZenEnvironment.production);
} else {
ZenConfig.applyEnvironment(ZenEnvironment.development);
}
runApp(MyApp());
}
Custom Configuration
ZenConfig.configure(
level: ZenLogLevel.info,
performanceTracking: true,
);
Query Defaults
final customDefaults = ZenQueryConfig(
staleTime: Duration(minutes: 5),
cacheTime: Duration(hours: 1),
retryCount: 3,
refetchOnFocus: true,
refetchOnReconnect: true,
);
π§ͺ Testing
Zenify is built for testing from the ground up.
void main() {
setUp(() {
Zen.testMode();
Zen.clearQueryCache();
});
tearDown(() {
Zen.reset();
});
test('counter increments', () {
final controller = CounterController();
expect(controller.count.value, 0);
controller.increment();
expect(controller.count.value, 1);
controller.dispose();
});
}
Mock dependencies easily:
test('user service test', () {
Zen.testMode()
.mock<ApiClient>(FakeApiClient())
.mock<AuthService>(FakeAuthService());
final userService = UserService();
final user = await userService.getCurrentUser();
expect(user.name, 'Test User');
});
π Complete Documentation
Core Guides
- Reactive Core Guide - Reactive values, collections, computed properties
- ZenQuery Guide - Async state, caching, mutations
- 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
- 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
- ποΈ 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
- core/zen_scope_manager
- core/zen_scope_stack_tracker
- debug/debug
- debug/zen_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
- 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