Zenify

pub package likes pub points license: MIT

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.

See complete example β†’


πŸ”₯ 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.

See ZenQuery Guide β†’

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)

See Reactive Core Guide β†’


πŸ’‘ 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),
)

See Effects Guide β†’

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:

  1. 5 minutes: Counter Example - Basic reactivity
  2. 10 minutes: Todo Example - CRUD with effects
  3. 15 minutes: ZenQuery Guide - Async state management
  4. 20 minutes: E-commerce Example - Real-world patterns

Building something complex?


πŸ“± 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:

  • ZenView for pages
  • Obx for reactive UI
  • ZenQueryBuilder for 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');
});

See Testing Guide β†’


πŸ“š Complete Documentation

Core Guides

Examples


πŸ™ 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


πŸ“„ 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:

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