Zenify

The state management framework that works the way Flutter works.

Automatic Query Caching • Scoped DI with Auto-Disposal • Offline-First • No Code Generation

pub package likes pub points codecov license: MIT

Zenify is a complete Flutter state management framework with a built-in TanStack Query engine, offline-first resilience, and hierarchical dependency injection — with zero boilerplate and no code generation.

// Hierarchical DI with automatic cleanup
scope.put<UserService>(UserService());
final service = scope.find<UserService>()!;

// Reactive state that just works
final count = 0.obs();
ZenObserver(() => Text('${count.value}'))  // Auto-rebuilds

// Infinite scroll — automatic page management with memory bounds
final feed = ZenInfiniteQuery<PostPage>(
  queryKey: 'feed',
  maxPages: 5,                    // 🚀 NEW: cap pages in RAM, evicts oldest
  initialPageParam: 1,
  infiniteFetcher: (page, _) => api.getPosts(page: page),
  getNextPageParam: (lastPage, all) => lastPage.hasMore ? all.length + 1 : null,
);

feed.fetchNextPage();             // append next page
feed.hasNextPage.value            // know when to stop
feed.isFetchingNextPage.value     // drive your loading footer
feed.data.value                   // all pages, reactive

// Global reactive status — anywhere in the app tree
Zen.isFetching                    // true if ANY query is in-flight
Zen.isMutating                    // true if ANY mutation is in-flight

🎯 The Problem

Every Flutter developer has written this. Probably many times:

// The async state boilerplate tax — paid on every API call, in every app
bool _isLoading = false;
String? _error;
User? _data;

Future<void> loadUser() async {
  setState(() => _isLoading = true);
  try {
    _data = await api.getUser(id);
    _error = null;
  } catch (e) {
    _error = e.toString();
  } finally {
    setState(() => _isLoading = false);
  }
}

// And if two widgets need the same data? Two separate API calls.
// And if the user goes offline? Blank screen.
// And if the data is stale? Manual cache invalidation logic.
// And if you navigate away and back? Fetch it all over again.

Now here's the same thing with Zenify:

ZenQueryConsumer<User>(
  queryKey: 'user:$id',
  fetcher: (_) => api.getUser(id),
  data: (user) => UserProfile(user),
  loading: () => const CircularProgressIndicator(),
  error: (e, retry) => ErrorView(e, onRetry: retry),
);

Automatic caching. Deduplication. Background refetch. Stale-while-revalidate. Offline resilience. All built in.

This is what TanStack Query brought to the web. Zenify brings it to Flutter — natively, without code generation, integrated directly into the framework's DI and reactivity system.


🏛️ Built on Flutter's Foundations — No Hidden Global Magic

This isn't just a design goal — it's enforced throughout the entire framework.

Scoped DI resolves dependencies by walking Flutter's native InheritedWidget tree. There is no mutable currentScope global pointer that changes on every navigation event.

Controller lifecycle is tied directly to State.dispose(). When the widget leaves the tree, the controller is disposed — no framework-level singleton registry involved.

Query cache is scoped per ZenRoute. Navigating away disposes the cache with the scope. There is no shared global cache that leaks state across different user sessions or routes.

Reactivity (.obs(), ZenObserver, ZenUpdater) is built on plain Dart objects and InheritedWidget notifications. There is no global event bus and no hidden dependency tracking.

The result: what you see in your widget tree is exactly what resolves your dependencies. Zenify is predictable, deeply linkable, and fully BuildContext-safe from the ground up — not as a documented limitation, but as an architectural guarantee.


Coming from GetX? The .obs() reactive syntax and DI verbs (put, find, delete) will feel familiar. Most migration is mechanical. GetX Migration Guide →

Upgrading from V1? The only mechanical change is adding a controller parameter to every ZenView.build() override. V2 Migration →


🏗️ Understanding Scopes (The Foundation)

Every Flutter developer has written this — the manual lifecycle tax:

// Without scoped DI — every feature carries this burden
class _ProfilePageState extends State<ProfilePage> {
  late final ProfileController _controller;
  late final UserRepository _repo;
  late final ProfileImageService _imageService;

  @override
  void initState() {
    super.initState();
    _controller = ProfileController();
    _repo = UserRepository(apiClient);
    _imageService = ProfileImageService(apiClient);
  }

  @override
  void dispose() {
    _controller.dispose(); // ← easy to forget
    _repo.dispose();       // ← easy to forget
    _imageService.dispose(); // ← easy to forget
    super.dispose();
    // Add a fourth dependency tomorrow? Add another dispose() call.
    // Forget one? Silent memory leak. Good luck finding it in production.
  }
}

Now here's the same thing with Zenify:

class ProfileModule extends ZenModule {
  @override
  void register(ZenScope scope) {
    final api = scope.find<ApiClient>()!;          // resolved from parent scope automatically
    scope.putLazy<UserRepository>(() => UserRepository(api));
    scope.putLazy<ProfileImageService>(() => ProfileImageService(api));
    scope.put<ProfileController>(ProfileController());
  }
}

ZenRoute(moduleBuilder: () => ProfileModule(), page: const ProfilePage())
// Navigate away → scope disposed → all three objects cleaned up. Automatically.
// Navigate back → fresh scope, fresh controllers, fresh state. Always.

One ZenRoute. Zero dispose() calls. Zero memory leaks. Zero stale state.

This is what hierarchical scoped DI means in practice. Zenify organizes dependencies into three levels with automatic lifecycle management. When a scope is destroyed, all its children are automatically cleaned up — the cascade is built in.

flowchart TD
    classDef root fill:#1A237E,stroke:#3949AB,stroke-width:2px,color:#fff
    classDef module fill:#004D40,stroke:#00897B,stroke-width:2px,color:#fff
    classDef page fill:#3E2723,stroke:#6D4C41,stroke-width:2px,color:#fff

    A["🌐 RootScope<br>(App Lifetime)"]:::root

    subgraph Feature_A [Module Scope]
        B["📦 AuthModule<br>(Feature Lifetime)"]:::module
        C["📄 LoginPage<br>(Page Lifetime)"]:::page
        D["📄 RegisterPage<br>(Page Lifetime)"]:::page
    end

    subgraph Feature_B [Module Scope]
        E["📦 CartModule<br>(Feature Lifetime)"]:::module
        F["📄 CheckoutPage<br>(Page Lifetime)"]:::page
    end

    A -->|"Zen.find()"| B
    A -->|"Zen.find()"| E
    B --> C
    B --> D
    E --> F

    note["♻️ Auto-disposed when<br>navigating away"]
    B -.- note
    E -.- note

The Three Scope Levels

RootScope (Global — App Lifetime)

  • Services like AuthService, CartService, ThemeService
  • Lives for entire app session
  • Access anywhere via Zen.find<CartService>() or the .to pattern: CartService.to.addItem()

Module Scope (Feature — Feature Lifetime)

  • Controllers shared across feature pages
  • Auto-dispose when leaving feature
  • Example: HR feature with CompanyControllerDepartmentControllerEmployeeController

Page Scope (Page — Page Lifetime)

  • Page-specific controllers
  • Auto-dispose when page pops
  • Example: LoginController, ProfileFormController

When to Use What

Scope Use For Lifetime
RootScope Needed across entire app App session
Module Scope Needed across a feature Feature navigation
Page Scope Needed on one page Single page

Learn more about hierarchical scopes →


🚀 Quick Start (30 seconds)

1. Install

dependencies:
  zenify: ^2.2.2

2. Initialize

void main() {
  Zen.init();
  runApp(const MyApp());
}

3. Create a Controller

class CounterController extends ZenController {
  final count = 0.obs();
  void increment() => count.value++;
}

4. Provide & Consume

// 1. Register — in your module, put the controller into its scope
class CartModule extends ZenModule {
  @override
  void register(ZenScope scope) {
    scope.put<CartController>(CartController());
  }
}

// In your router — ZenRoute creates the scope and runs the module:
ZenRoute(
  moduleBuilder: () => CartModule(),
  page: const CartPage(),
)

// 2. Consume — controller is injected directly into build(), zero lookup code
class CartPage extends ZenView<CartController> {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, CartController controller) {
    return Column(
      children: [
        // 3. React — auto-rebuilds when itemCount changes
        ZenObserver(() => Text('${controller.itemCount.value} items')),
        ElevatedButton(
          onPressed: controller.checkout,
          child: const Text('Checkout'),
        ),
      ],
    );
  }
}

That's it. The controller is scoped to the route, auto-disposed on pop, multi-instance safe.

See complete example →


🔥 Core Features

1. Smart Async State (ZenQuery)

React Query patterns built on the reactive system. Say goodbye to manual isLoading flags.

sequenceDiagram
    participant UI as ZenQueryBuilder
    participant Cache as ZenQuery Cache
    participant API as Network / DB

    UI->>Cache: 1. Request 'user:123'
    alt Cache is Fresh
        Cache-->>UI: 2. Return cached data immediately
    else Cache is Stale
        Cache-->>UI: 2. Return stale data instantly
        Cache->>API: 3. Fetch fresh data in background
        API-->>Cache: 4. New data arrives
        Cache-->>UI: 5. UI reactively updates
    else Cache is Empty
        Cache-->>UI: 2. Yield 'loading' state
        Cache->>API: 3. Fetch data
        API-->>Cache: 4. Data arrives
        Cache-->>UI: 5. Yield 'data' state
    end

Path A — Inline (no controller needed):

ZenQueryConsumer<User>(
  queryKey: 'user:123',
  fetcher: (_) => api.getUser(123),
  data: (user) => UserProfile(user),
  loading: () => const CircularProgressIndicator(),
  error: (error, retry) => ErrorView(error, onRetry: retry),
);

Path B — Shared (query in controller, multiple widgets read it):

class UserController extends ZenController {
  late final query = ZenQuery<User>(
    queryKey: 'user:123',
    fetcher: (_) => api.getUser(123),
    config: ZenQueryConfig(staleTime: Duration(minutes: 5)),
  );
}

ZenQueryBuilder<User>(
  query: controller.query,
  builder: (context, user) => UserProfile(user),
  loading: () => const 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
  • ✅ Optimistic updates with rollback
  • ✅ Infinite scroll pagination with maxPages memory bounds
  • ✅ Real-time streams support
  • ✅ Tag & wildcard group invalidation
  • Zen.isFetching & Zen.isMutating global reactive status hooks

See ZenQuery Guide →

2. Offline-First Resilience

Offline support isn't a plugin you add. It's how Zenify's query engine is architected. Queries return stale data from disk instantly while fetching fresh data in the background. Mutations that fail when offline are queued, persisted, and automatically replayed when the connection returns.

// Auto-persist data to disk — survives app restarts
final postsQuery = ZenQuery<List<Post>>(
  queryKey: 'posts',
  fetcher: (_) => api.getPosts(),
  config: ZenQueryConfig(
    persist: true,
    networkMode: NetworkMode.offlineFirst,
  ),
);

// Queue mutations when offline — auto-replay when back online
final createPost = ZenMutation<Post, Post>(
  mutationKey: 'create_post',
  mutationFn: (post) => api.createPost(post),
);

Key capabilities:

  • Storage agnostic — Hive, SharedPreferences, SQLite, or any ZenStorage implementation
  • Mutation queue — Actions queued and auto-replayed on reconnect
  • Optimistic updates — Update UI immediately, sync later
  • Network modes — Control how queries behave offline (offlineFirst, online, always)

See Offline Guide →

3. ZenView — Pages, Screens & Widgets

By extending ZenView<T>, the controller is injected directly into build(). No context.read<T>(), no BlocBuilder, no ref.watch(). The controller arrives as a typed parameter — see the Quick Start above for the full Register → Consume → React pattern.

React — pick your reactivity tool:

// Rx<T> values — auto-rebuilds on change:
ZenObserver(() => Text('${controller.count.value}'))

// Manual update() calls — selective rebuilds:
ZenUpdater<CartController>(
  builder: (ctx, ctrl) => CartBadge(count: ctrl.itemCount),
)

// Async/API state — declarative:
controller.productsQuery.when(
  data: (products) => ProductList(products: products),
  loading: () => const CircularProgressIndicator(),
  error: (e) => Text('Error: $e'),
)

Which pattern for which situation?

Situation Pattern
App-level services (auth, analytics, database) Zen.registerModules([AppModule()]) at startup
Route with multiple dependencies ZenRoute(moduleBuilder: () => FeatureModule())
Simple route, single controller ZenProvider.create<T>(create: ...)
App-level singleton (prototyping) Zen.put<T>(...) at startup

4. Hierarchical DI with Auto-Cleanup

Organize dependencies naturally with feature-based modules and parent-child scopes.

// 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 on navigation)
class UserModule extends ZenModule {
  @override
  void register(ZenScope scope) {
    final db = scope.find<DatabaseService>()!;
    scope.putLazy<UserRepository>(() => UserRepository(db));
    scope.putLazy<UserController>(() => UserController());
  }
}

// Use with any router — it's just a widget
ZenRoute(
  moduleBuilder: () => UserModule(),
  page: const UserPage(),
)

Core API:

  • Zen.put<T>() — Register in root scope
  • Zen.find<T>() — Retrieve (throws if missing)
  • Zen.findOrNull<T>() — Retrieve (returns null if missing)
  • Zen.exists<T>() — Check existence
  • Zen.delete<T>() — Remove from scope
  • Zen.putLazy<T>() — Register a lazy factory

Works with: GoRouter, AutoRoute, Navigator 2.0, any router.

See Hierarchical Scopes Guide →

5. Zero-Boilerplate Reactivity

Reactive system built on Flutter's ValueNotifier. Simple, fast, no magic.

class TodoController extends ZenController {
  final todos = <Todo>[].obs();
  final filter = Filter.all.obs();

  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();
    }
  }

  void addTodo(String title) => todos.add(Todo(title));
}

// In UI — automatic, minimal rebuilds
ZenObserver(() => Text('${controller.todos.length} todos'))
ZenObserver(() => ListView.builder(
  itemCount: controller.filteredTodos.length,
  itemBuilder: (context, i) => TodoItem(controller.filteredTodos[i]),
))

For manual/selective rebuilds, use ZenUpdater:

// Controller:
controller.update(['counter']); // Only notifies 'counter' listeners

// Widget:
ZenUpdater<CounterController>(
  id: 'counter',
  builder: (context, ctrl) => Text('${ctrl.count}'),
)

See Reactive Core Guide →


💡 Common Patterns

Global Services with .to Pattern

class CartService extends ZenService {
  static CartService get to => Zen.find<CartService>();

  final items = <CartItem>[].obs();
  void addToCart(Product product) => items.add(CartItem.fromProduct(product));
}

// Register once at startup
Zen.put<CartService>(CartService(), isPermanent: true);

// Use anywhere — widgets, controllers, helpers
CartService.to.addToCart(product);

Global Reactive State (Theme, Auth, Settings)

For app-wide reactive state that belongs to no single page, use Zen.put + the .to pattern + ZenObserver. Do not use ZenView for thisZenView is strictly for page-level controllers scoped to a route.

class ThemeController extends ZenController {
  static ThemeController get to => Zen.find<ThemeController>()!;

  final isDark = false.obs();
  final accentColor = Colors.blue.obs();

  void toggleDark() => isDark.value = !isDark.value;
}

// Register once at app startup — isPermanent keeps it alive for the full app session
Zen.put<ThemeController>(ThemeController(), isPermanent: true);

// React to it anywhere in the widget tree — no ZenView, no ZenProvider needed
ZenObserver(() => Icon(
  ThemeController.to.isDark.value ? Icons.dark_mode : Icons.light_mode,
))

// Access imperatively in callbacks
ElevatedButton(
  onPressed: () => ThemeController.to.toggleDark(),
  child: const Text('Toggle Theme'),
)

Rule of thumb: If a controller belongs to one route → ZenProvider.create + ZenView. If a controller is truly app-wide → Zen.put + .to + ZenObserver.

Infinite Scroll Pagination & Memory Limits

final postsQuery = ZenInfiniteQuery<PostPage>(
  queryKey: ['posts'],
  maxPages: 3, // 🚀 Memory bounds: Keep at most 3 pages in RAM, evicting oldest
  infiniteFetcher: (cursor, token) => api.getPosts(cursor: cursor),
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
);

// Auto-load next page when reaching end
if (index == postsQuery.data.length - 1) postsQuery.fetchNextPage();

Global Query & Mutation Hooks (Zen.isFetching, Zen.isMutating)

Show app-wide loading bars, spinners, or save overlays effortlessly without prop drilling:

// Global progress bar at the top of the app
ZenObserver(() {
  if (Zen.isFetching) {
    return const LinearProgressIndicator();
  }
  return const SizedBox.shrink();
})

// Global save/busy indicator for mutations
ZenObserver(() {
  if (Zen.isMutating) {
    return const SavingBadge();
  }
  return const SizedBox.shrink();
})

Optimistic Updates

// Easy way — helpers handle rollback automatically
final createPost = ZenMutation.listPut<Post>(
  queryKey: 'posts',
  mutationFn: (post) => api.createPost(post),
  onError: (err, post) => logger.error('Create failed', err),
);

// Advanced — full control
final mutation = ZenMutation<User, UpdateArgs>(
  onMutate: (args) => userQuery.data.value = args.toUser(),
  onError: (err, args, old) => userQuery.data.value = old,
);

Real-Time Streams

final chatQuery = ZenStreamQuery<List<Message>>(
  queryKey: 'chat',
  streamFn: () => chatService.messagesStream,
);

🛠️ Advanced Features

  • Global status hooksZen.isFetching & Zen.isMutating for app-wide loading indicators with zero prop-drilling
  • Memory-bounded paginationZenInfiniteQuery.maxPages caps RAM usage during long scroll sessions
  • Filtered fetch statusZen.queryCache.isFetching(tag: ...) checks status for any subset of queries
  • Effects — Automatic loading/error/success state management (guide)
  • Workersever, debounce, throttle, interval, condition reactive handlers
  • Computed values — Auto-updating derived state
  • Performance control — Fine-grained: ZenObserver (reactive) or ZenUpdater (manual)
  • DevTools — Built-in scope/query inspector

📱 Widget Quick Reference

Widget Use When Rebuilds On
ZenView Building pages with controllers Controller injected into build()
ZenRoute Need module/scope per route Route navigation
ZenObserver Fine-grained reactive updates .obs() value changes
ZenUpdater Manual control over rebuild timing controller.update() call
ZenConsumer Access a controller, no rebuild Never (manual)
ZenQueryConsumer Fetch data inline, no controller needed Query state changes
ZenQueryBuilder Shared query instance across widgets Query state changes
ZenStreamQueryBuilder Real-time data streams Stream events
ZenEffectBuilder Async operations with loading/error states Effect state changes

90% of the time, you'll use:

  • ZenView for pages
  • ZenObserver for reactive UI
  • ZenQueryConsumer for simple API calls
  • ZenQueryBuilder when the query is shared across widgets

🔧 Configuration

void main() {
  Zen.init();

  // Optional: configure logging
  ZenConfig.applyEnvironment(ZenEnvironment.development);

  // Optional: set global query defaults
  Zen.queryCache.setDefaultConfig(ZenQueryConfig(
    staleTime: Duration(minutes: 5),
    cacheTime: Duration(hours: 1),
  ));

  runApp(const MyApp());
}

🧪 Testing

Built for testing from the ground up:

void main() {
  setUp(() {
    Zen.testMode().clearQueryCache();
  });
  tearDown(() => Zen.reset());

  test('counter increments', () {
    final controller = CounterController();
    controller.onInit();
    controller.increment();
    expect(controller.count.value, 1);
    controller.dispose();
  });

  test('mock dependencies', () {
    Zen.testMode().mock<ApiClient>(FakeApiClient());
    // All code that calls Zen.find<ApiClient>() gets the mock
  });

  test('query with in-memory storage', () async {
    Zen.queryCache.setStorage(InMemoryStorage()); // built-in, zero deps
    final q = ZenQuery<String>(
      queryKey: 'test',
      fetcher: (_) async => 'hello',
      config: ZenQueryConfig(persist: true, toJson: (s) => {'v': s}, fromJson: (j) => j['v']),
    );
    await q.fetch();
    expect(q.data.value, 'hello');
  });
}

See complete testing guide →


⬆️ Migrating from V1

V2 has several breaking changes. The most impactful is mechanical:

// ❌ V1 — magic getter, global registry
class CartPage extends ZenView<CartController> {
  @override
  Widget build(BuildContext context) {
    return Text('${controller.totalItems}');
  }
}

// ✅ V2 — explicit injection, tree-bound
class CartPage extends ZenView<CartController> {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, CartController controller) {
    return Text('${controller.totalItems}');
  }
}

Full change summary:

V1 V2 Impact
build(BuildContext context) + magic getter build(BuildContext context, T controller) Breaking — compiler enforces it
ZenScopeWidget / ZenScopeWidget.create ZenProvider / ZenProvider.create Breaking — rename
ZenControllerScope<T>() Removed — use ZenProvider.create<T>() Breaking — must migrate
initController override on ZenView Removed — use ZenProvider.create at callsite Breaking — must migrate
ZenBuilder<T> ZenUpdater<T> — fully renamed Breaking — rename required
Global Zen.put for UI controllers ZenProvider scope — no global fallback Architectural shift

Full V2 Migration Guide →


🔍 Flutter DevTools Extension

Zenify has a separate DevTools extension package for real-time inspection and debugging.

Quick Setup

dev_dependencies:
  zenify_devtools_extension: ^1.0.0
void main() {
  Zen.init(registerDevTools: true); // registers extensions automatically
  runApp(const MyApp());
}

3-Tab Inspector:

  1. Scope Inspector — Visualize your entire DI hierarchy
  2. Query Cache Viewer — Monitor, refetch, and invalidate queries
  3. Metrics Dashboard — Live metrics to identify bottlenecks

Learn more →


🎓 Learning Path

New to Zenify? Start here:

  1. 5 minutes: Counter Demo — Basic reactivity
  2. 10 minutes: Todo Demo — CRUD with effects
  3. 15 minutes: ZenQuery Guide — Async state management
  4. 20 minutes: E-commerce Case Study — Real-world patterns
  5. 30 minutes: Offline Demo — Full offline-first app

Building something complex?


📚 Complete Documentation

Core Guides

Demos & Showcase

Run the unified showcase application (cd example && flutter run) or explore individual demo directories:


🙏 Acknowledgements

  • TanStack Query by Tanner Linsley — For proving that async state deserves a first-class engine
  • Riverpod by Remi Rousselet — For hierarchical scoping patterns in Flutter
  • GetX by Jonny Borges — For pioneering terse reactive syntax in Flutter

💬 Community & Support


📄 License

MIT License — see LICENSE file


🚀 Ready to Get Started?

flutter pub add zenify

Choose your path:

Experience the zen of Flutter development.

Libraries

controllers/controllers
controllers/zen_controller
controllers/zen_service
core/core
core/zen_config
core/zen_environment
core/zen_exception
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
Service extensions for DevTools integration
devtools/service_extensions
di/di
di/zen_di
di/zen_lifecycle
di/zen_reactive
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
storage/storage
Zenify Storage Adapters
storage/zen_in_memory_storage
testing/testing
testing/zen_test_mode
testing/zen_test_utilities
utils/utils
utils/zen_scope_inspector
utils/zen_utils
widgets/builders/zen_effect_builder
widgets/builders/zen_infinite_query_when
widgets/builders/zen_mutation_when
widgets/builders/zen_query_builder
widgets/builders/zen_query_consumer
widgets/builders/zen_query_when
widgets/builders/zen_stream_query_builder
widgets/builders/zen_updater
widgets/components/rx_widgets
widgets/components/zen_route
widgets/components/zen_view
widgets/scope/zen_consumer
widgets/scope/zen_provider
widgets/widgets
workers/workers
workers/zen_workers
zenify
Zenify - Modern Flutter state management