From c80a4f6afc2325b00dd5198958468788b2830190 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sat, 8 Aug 2026 09:35:32 -0400 Subject: [PATCH] Mobile v4 docs: validation page + durable-callbacks coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New the-basics/validation.md (order 158): validate()/validateOnly() semantics (abort handler, keep state, render with $errors — identical on device and web), the repeatable #[Validate] attribute with eager per-sync validation and rule merging, rules()/messages(), inline rule scoping, FormRequest sharing (method injection, props-only data scope, authorize() never called), the explicit-only error-display contract (@error, @nativeError, input error/supporting attributes), manual bag control, and per-component child scoping. the-basics/events.md additions: both #[On] spellings (idempotent native: prefix), the fluent Pending* outcome callbacks and onSuccess() sugar, callback durability (two-tier storage; $this-closures survive a process kill via serialize-and-rebind, with the serializable-captures / no-eval / persistent-cache constraints; method-name strings as the most robust form), and per-listener validation isolation, cross-linked to the new validation page. Every documented fact verified against the live core source and its test suites (26 validation + 8 durable-callback tests). Prose Blade directives are @@-escaped per site convention — docs .md pages compile through Blade, and an unescaped bare @error in prose is an unclosed if. Co-Authored-By: Claude Fable 5 --- .../views/docs/mobile/4/the-basics/events.md | 62 ++++ .../docs/mobile/4/the-basics/validation.md | 302 ++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 resources/views/docs/mobile/4/the-basics/validation.md diff --git a/resources/views/docs/mobile/4/the-basics/events.md b/resources/views/docs/mobile/4/the-basics/events.md index dd2ca72f..5ad717c7 100644 --- a/resources/views/docs/mobile/4/the-basics/events.md +++ b/resources/views/docs/mobile/4/the-basics/events.md @@ -35,6 +35,10 @@ class ChatScreen extends NativeComponent `#[On]` is repeatable — stack several on one method to handle multiple events, or put several methods on the same event. Listeners are torn down automatically when the screen unmounts, so they never leak onto the next screen. +Pass the bare event class, as above. The older `'native:'`-prefixed spelling — `#[On('native:' . MessageReceived::class)]` — +also works: the prefix is applied idempotently, so existing prefixed examples keep behaving identically and are never +double-prefixed. + ## Listening with ->on() For a listener you register at runtime — for example inside `mount()`, or conditionally — use the fluent `->on()` @@ -52,6 +56,61 @@ public function mount(): void Use `#[On]` for the common case (a fixed listener declared on the class) and `->on()` when you need to wire one up dynamically. +## Callbacks on native calls + +Async native APIs return a `Pending*` builder you can chain outcome callbacks onto directly, instead of declaring a +listener on the class. Each outcome event gets a fluent method named after the event class: + +```php +use Native\Mobile\Facades\Camera; + +Camera::getPhoto() + ->photoTaken(fn ($event) => $this->path = $event->path) + ->photoCancelled(fn () => $this->status = 'cancelled'); +``` + +The callback receives the event object and fires once — the first outcome consumes the registration, so a success +callback and its cancel/denied siblings are mutually exclusive. + +Every builder also has `onSuccess()`, generic sugar for that builder's success event. It reads the same on every +builder, so you don't need to remember which event a given API resolves with: + +```php +use Native\Mobile\Events\Gallery\MediaSelected; + +Camera::pickImages('image')->onSuccess(function (MediaSelected $media) { + $this->images = $media->files; +}); +``` + +`onSuccess()` registers the exact same callback the event-named form would — `->mediaSelected(...)`, +`->photoTaken(...)` and friends are unchanged, and if you override the outcome with `->event(Custom::class)`, +`onSuccess()` follows the override. + +## Callback durability + +The process that registers a callback may not be the process that receives the result — the OS can kill the app +while the camera or picker is in the foreground. Callbacks are therefore stored in two tiers: a warm in-memory copy, +plus a durable serialized copy in the cache. When the event arrives in a fresh process, the durable copy fires on the +live component as if nothing happened. + +The three callback forms differ in how robustly they cross that boundary: + +- **Closures** — including ones that use `$this` — are serialized durably; at fire time the closure is rebound to the + live component, so `$this` works again, private members included. Captured `use` variables must themselves be + serializable (a resource or PDO handle can't cross), and closures defined in eval'd code can't round-trip. When + serialization fails, the warm copy still fires — the callback just won't survive a kill. +- **Method-name strings** — `->mediaSelected('onMediaSelected')` resolves the method on the live component when the + event arrives. Serializes trivially, so this is the most robust form. +- **Invokable class-strings** — resolved from the container and invoked with the event. + + + ## Where events come from Native events originate on the device side and are delivered to whichever screen is alive: plugin events (a @@ -59,6 +118,9 @@ Native events originate on the device side and are delivered to whichever screen events an async native call resolves with. Because delivery targets the live screen, a listener only fires while its screen is on the stack. +Listeners for the same event are guarded independently: a [validation](validation) failure inside one listener +records its errors and aborts that listener only — the other listeners still receive the event. +