Events¶
Events are the typed contract of the Python↔Kotlin boundary. When the native side reports a tap or a value change, the payload arrives raw and is validated before entering a handler — like FastAPI validates a request body.
Event types¶
All inherit from Event (frozen Pydantic).
| Event | Fields | Emitted by |
|---|---|---|
TapEvent |
x: float \| None, y: float \| None |
Button.on_click |
TextChangeEvent |
value: str, valid: bool (against the input's pattern) |
Input.on_change, TextArea.on_change |
ToggleEvent |
checked: bool |
Checkbox.on_change, Switch.on_change |
SlideEvent |
value: float |
Slider.on_change |
DateChangeEvent |
value: str (ISO yyyy-mm-dd) |
DatePicker.on_change |
FileSelectEvent |
uri: str, name: str \| None |
FilePicker.on_select |
These are the core events — there are 31 in total
The table above shows the most common ones. Track E added many more —
navigation (RouteChangeEvent/PageChangeEvent), lists (ScrollEvent/
EndReachedEvent/RefreshEvent), gestures (PanEvent/ScaleEvent/
SwipeEvent/ReorderEvent/LongPressEvent/DragEvent), forms
(SubmitEvent/ValidationEvent/RangeChangeEvent/TimeChangeEvent/
SelectEvent), overlays
(DismissEvent/MenuSelectEvent) and platform (SensorEvent/
LifecycleEvent/ConnectivityEvent/DeepLinkEvent/QrScanEvent/
ThemeChangeEvent/LocaleChangeEvent). List the full contract with
tempest spec or see the API reference.
The validation gate: parse_event¶
parse_event(event_type, raw) turns a raw payload (a mapping) into a typed event,
or raises EventValidationError with structured per-field errors (JSON-
serializable):
from tempestroid import EventValidationError, TextChangeEvent, parse_event
event = parse_event(TextChangeEvent, {"value": "hi"}) # -> TextChangeEvent(value="hi")
try:
parse_event(TextChangeEvent, {}) # missing required field
except EventValidationError as exc:
print(exc.errors) # [{"loc": ("value",), "type": "missing", ...}]
Handlers¶
A handler may receive the typed event or be zero-argument when the value is not needed. The runtime detects the arity and passes (or omits) the event:
# Receives the typed event:
def on_name(event: TextChangeEvent) -> None:
app.set_state(lambda s: setattr(s, "name", event.value))
# Zero-argument (ignores the payload):
Button(label="+", on_click=lambda: app.set_state(...))
Handlers may be sync or async — the runtime schedules coroutines on the asyncio
loop without freezing the UI.
Typed handler alias¶
To annotate handler props, the package exports EventHandler — the generic
typed handler-prop wrapper (e.g. on_click, on_change). It carries a
WithJsonSchema annotation so handler-bearing widgets don't break JSON-schema
generation.
The contract as data¶
Each widget declares the event each handler emits via the event_schemas
classvar. The introspect() function
publishes all of this as JSON — widget prop schemas, the event each handler emits,
and each event's payload schema. This is what powers tempest spec and the device
boundary.
Recap¶
- Events are frozen Pydantic models (
TapEvent,TextChangeEvent, …). parse_eventis the gate that validates the raw payload before the handler — like FastAPI validating a request body.- Handlers may take the typed event or be zero-argument; sync or
async. - The contract (
event_schemas+introspect()) is published as JSON bytempest spec.
Next steps¶
➡️ Inspect the contract with the CLI (tempest spec), or see real
handlers in the Example gallery.