zenify-mastery
Comprehensive guide for Zenify state management framework (V2+). Trigger this when working on Flutter apps using Zenify for dependency injection, reactive state (.obs), ZenQuery (caching/offline), controllers, or route-bound modules.
معلومات المصدر
- المستودع
- sdegenaar/zenify
- آخر نشاط في المصدر
- ١ سبتمبر ٢٠٢٦ في ٠٢:٠٧
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٣٧
- التفرعات
- ٢
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- name
- zenify-mastery
- description
- Comprehensive guide for Zenify state management framework (V2+). Trigger this when working on Flutter apps using Zenify for dependency injection, reactive state (.obs), ZenQuery (caching/offline), controllers, or route-bound modules.
# Zenify (V2+) Mastery Skill
Zenify is a tree-scoped Flutter state management framework combining hierarchical DI, fine-grained reactivity, and server-state management (`ZenQuery`). Zero global singletons. Zero code generation. All lifecycle disposal is automatic.
> **2,117 tests · 0 failures · >95% coverage** as of V2.0 release.
>
> For deep dives, see the **[Reference Docs](#deep-dive-references)** at the bottom.
---
## 1. Core Mental Model: 3-Tier Hierarchical Scoping
Every dependency is tree-bound via `BuildContext` — just like `Theme.of(context)`.
```
┌──────────────────────────────────────────────────────────────┐
│ RootScope (App Lifetime) │
│ • True singletons: AuthService, ApiService, etc. │
│ • Register: Zen.put(AuthService()) — ZenService auto-perm │
│ • Access: AuthService.to / Zen.find<AuthService>() │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────┐
│ ModuleScope (Feature / Route Lifetime) │
│ • Shared controllers across a multi-page flow │
│ • Register: ZenRoute(moduleBuilder: () => MyModule(), ...) │
│ • Access: ZenView<T> / ZenConsumer<T> │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────┐
│ PageScope (Page / Local Lifetime) │
│ • Single-page UI controllers │
│ • Register: ZenProvider.create<T>(create: () => T(), ...) │
│ • Access: ZenView<T> / ZenConsumer<T> │
└──────────────────────────────────────────────────────────────┘
```
| Needed where? | Use |
| :--------------- | :---------------------------------------------------- |
| Entire app | `ZenService` + `Zen.put()` + `.to` static getter |
| Across a feature | `ZenModule` + `ZenRoute(moduleBuilder: ...)` |
| One page only | `ZenProvider.create<T>(create: () => T())` + `ZenView<T>` |
---
## 2. Registration (V2 API — Exact Params)
```dart
// Module — V2 API: register(ZenScope scope) + scope.put<T>()
class CheckoutModule extends ZenModule {
@override
String get name => 'CheckoutModule';
@override
void register(ZenScope scope) {
scope.put<CheckoutController>(
CheckoutController(
cart: Zen.find<CartService>()!, // root service — global lookup
payment: scope.find<PaymentService>()!, // ancestor scope — tree walk
),
);
scope.put<AddressController>(AddressController());
}
}
// ZenRoute — V2 API: moduleBuilder: and page: (NOT module: or child:)
GoRoute(
path: '/checkout',
builder: (context, state) => ZenRoute(
moduleBuilder: () => CheckoutModule(),
page: const CheckoutPage(),
scopeName: 'CheckoutScope', // optional — aids debug logs
),
);
// Single-controller shortcut — no module needed
ZenProvider.create<LoginController>(
create: () => LoginController(auth: Zen.find<AuthService>()!),
child: const LoginPage(),
)
```
**Scope inheritance rules:**
- `ZenRoute` connects to `Zen.rootScope` as parent → `scope.find<T>()` resolves root services ✅
- `ZenProvider` (bare) does NOT → use `Zen.find<T>()` inside controllers instead ✅
---
## 3. Consuming Controllers
```dart
// ZenView<T> — primary pattern; controller injected into build() — compiler-enforced
class CheckoutPage extends ZenView<CheckoutController> {
const CheckoutPage({super.key});
@override
Widget build(BuildContext context, CheckoutController controller) {
return Scaffold(
body: ZenObserver(() => Text('\$${controller.total.value}')),
floatingActionButton: FloatingActionButton(
onPressed: controller.placeOrder,
child: const Icon(Icons.check),
),
);
}
}
// ZenConsumer<T> — for sub-widgets that can't extend ZenView
ZenConsumer<CheckoutController>(
builder: (context, controller) => Text('\$${controller.total.value}'),
);
// context.controller<T>() — imperative, throws ZenControllerNotFoundException if missing
final ctrl = context.controller<CheckoutController>();
// Global services — .to pattern (works anywhere, including widgets)
ZenObserver(() => Text('${CartService.to.items.length} items'))
```
> ❌ **V1 pattern removed:** `Widget build(BuildContext context)` with a magic `controller` getter no longer exists.
---
## 4. Reactivity
```dart
// Declare
final count = 0.obs(); // RxInt
final name = ''.obs(); // Rx<String>
final isLoading = false.obs(); // RxBool — .toggle(), .isTrue, .isFalse
final user = Rx<User?>(null); // Nullable
final items = <String>[].obs(); // RxList — mutating methods auto-notify
final map = <String, dynamic>{}.obs(); // RxMap
final tags = <String>{}.obs(); // RxSet
// Computed — auto-tracks deps, lazy
late final greeting = computed(() => 'Hello, ${name.value}!');
// Observe in UI — wrap MINIMAL subtree
ZenObserver(() => Text('Count: ${controller.count.value}'))
// ZenUpdater<T> — for non-reactive state; rebuilds on controller.update()
ZenUpdater<ManualController>(
id: 'counter',
builder: (context, ctrl) => Text('${ctrl.count}'),
)
```
---
## 5. Controllers & Services
```dart
class ProfileController extends ZenController {
final UserService _svc;
ProfileController(this._svc);
final name = ''.obs();
@override void onInit() { super.onInit(); _load(); }
Future<void> _load() async => name.value = (await _svc.getProfile()).name;
// Lifecycle: onInit → onReady → onPause / onResume → onClose
}
class AuthService extends ZenService {
// ZenService extends ZenController — auto-permanent, no isPermanent: true needed
static AuthService get to => Zen.find<AuthService>()!;
final isAuthenticated = false.obs();
}
// main.dart
void main() async {
await Zen.init();
Zen.put(AuthService()); // ZenService — auto-permanent ✅
Zen.put<ThemeController>( // plain ZenController — explicit flag required ✅
ThemeController(), isPermanent: true,
);
runApp(const MyApp());
}
```
---
## 6. Workers
```dart
// All 4 workers are shorthand methods on ZenController (call inside onInit)
debounce(query, (val) => search(val), time: const Duration(milliseconds: 300));
ever(query, (val) => log(val));
once(query, (val) => analytics.track(val));
interval(query, (val) => suggest(val), time: const Duration(seconds: 1));
// ZenWorkers.* static factory returns a ZenWorker with .pause()/.resume()/.dispose()
```
---
## 7. ZenQuery (Async Server State)
```dart
// Define — in a controller
late final userQuery = ZenQuery<User>(
queryKey: ['user', 'current'],
fetcher: (token) async {
final ct = CancelToken(); // e.g. Dio's CancelToken
token.onCancel(() => ct.cancel()); // wire cancel token
return api.fetchCurrentUser(cancelToken: ct);
},
config: const ZenQueryConfig(
staleTime: Duration(minutes: 5),
cacheTime: Duration(hours: 1),
retryCount: 3,
persist: true, // requires ZenStorage registered
enableBackgroundRefetch: true,
),
tags: ['user', 'profile'],
);
// Consume — .when() shorthand (cleanest)
controller.userQuery.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, retry) => ErrorView(e, onRetry: retry),
);
// Invalidate
ZenQueryCache.instance.invalidateQuery('user:current');
ZenQueryCache.instance.invalidateQueries(ZenQueryFilter(tags: ['user']));
```
> **ZenQuery vs ZenEffect:** Use `ZenQuery` for server data that benefits from caching/offline. Use `ZenEffect` for one-shot operations (save, submit, upload) with no caching.
---
## 8. GoRouter — Key Patterns
```dart
// Basic: moduleBuilder + page params
GoRoute(
path: '/profile/:id',
builder: (context, state) => ZenRoute(
moduleBuilder: () => ProfileModule(state.pathParameters['id']!),
page: const ProfilePage(),
),
);
// ShellRoute — canonical V2 hierarchical DI (child scopes inherit automatically)
ShellRoute(
builder: (context, state, child) => ZenRoute(
moduleBuilder: () => AppShellModule(),
page: AppShell(child: child),
),
routes: [
GoRoute(
path: '/feed',
builder: (context, state) => ZenRoute( // inherits AppShell scope — zero config
moduleBuilder: () => FeedModule(),
page: const FeedPage(),
),
),
],
)
```
---
## 9. Widget Taxonomy
| Widget | Role | When to Use |
| :--- | :--- | :--- |
| `ZenProvider` | Provide — scope from a module | Multi-controller feature roots |
| `ZenProvider.create<T>` | Provide — single controller | Simple single-controller pages |
| `ZenRoute` | Provide — scope + route lifecycle | GoRouter / Navigator routes |
| `ZenView<T>` | Consume (extend) — page base class | Pages, screens |
| `ZenConsumer<T>` | Consume (compose) — inline builder | Sub-widgets |
| `ZenObserver` | React — rebuilds on `Rx<T>` changes | Reactive value display |
| `ZenUpdater<T>` | React — rebuilds on `update()` | Manual / batched rebuilds |
| `ZenQueryBuilder<T>` | Async — observes `ZenQuery` | Controller-owned queries |
| `ZenQueryConsumer<T>` | Async — self-contained query | Standalone queries |
| `ZenEffectBuilder<T>` | Async — observes `ZenEffect` | One-shot async operations |
**Key principle:** DI access and reactivity are separate concerns — compose `ZenView` + `ZenObserver`, never conflate them.
---
## 10. Critical Anti-Patterns
| ❌ ANTI-PATTERN | ✅ IDIOMATIC V2 |
| :--- | :--- |
| V1 `void dependencies() { register(() => ...) }` | `void register(ZenScope scope) { scope.put<T>(...) }` |
| `ZenRoute(module: M(), child: Page())` | `ZenRoute(moduleBuilder: () => M(), page: Page())` |
| `build(BuildContext context)` + magic `controller` getter | `build(BuildContext context, MyController controller)` |
| `Zen.put(CartController())` for UI state | `ZenProvider.create` or `ZenRoute` + module |
| Reading `.value` outside `ZenObserver` | Wrap in `ZenObserver(() => ...)` |
| `FutureBuilder` for network data | `ZenQuery` + `.when()` |
| `isPermanent: true` on `Zen.put(MyService())` | Just `Zen.put(MyService())` — `ZenService` is auto-permanent |
| `ZenView<T>` to consume a `Zen.put<T>()` controller | `ZenObserver` + `.to` static getter |
---
## 11. Migration Cheat Sheet
| From GetX | From Riverpod / Provider | Zenify V2 |
| :--- | :--- | :--- |
| `Get.put(Ctrl())` | `ChangeNotifierProvider` | `ZenRoute(moduleBuilder:...)` / `ZenProvider.create` |
| `Get.find<Ctrl>()` | `Provider.of<T>(context)` | `context.controller<T>()` / `Zen.find<T>()` |
| `Obx(() => ...)` | `Consumer<T>` / `ref.watch` | `ZenObserver(() => ...)` |
عرض على GitHubملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub