Navigation Hooks
Run code as a screen is left or arrived at — and, when you need to, stop a navigation before it happens.
$beforeNavigate
Runs on the way out. In its simplest form it is a place to save and clean up:
$beforeNavigate {
Db.save()
analytics.track("left_screen")
} Blocking a navigation
Take a cancel parameter and the hook can refuse. This is the unsaved-changes
guard:
$beforeNavigate(({ cancel }) => {
if (hasUnsavedChanges) {
showDialog = true
cancel()
}
}) cancel form has to run before the navigation commits,
which means the system preview cannot play. If you want a gesture-driven animation and a
guard, drive the animation with $back instead.| Form | When it runs | System back preview |
|---|---|---|
$beforeNavigate { … } | During disposal | Works |
$beforeNavigate(({ cancel }) => …) | Before navigating | Suppressed |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
cancel | () -> Unit | Call to stop the navigation |
from | NavigationTarget | The screen being left |
to | NavigationTarget | Where it was heading |
$afterNavigate
Runs on the way in — analytics, focus, anything that should happen per arrival:
$afterNavigate {
analytics.track("screen_view")
}
$afterNavigate(({ from, to }) => {
analytics.track("navigated", from?.path, to.path)
}) | Parameter | Type | Meaning |
|---|---|---|
from | NavigationTarget? | Previous screen — null on first load |
to | NavigationTarget | The screen just arrived at |
$afterNavigate runs on every arrival, including coming back to a screen that was
already on the stack. $onMount runs when the screen is composed.
For "track a screen view" you want this one; for "load once" you want $onMount.The $back driver
Back is also a gesture, and a gesture has a progress value. $back exposes it as a morph driver, so a screen can animate under the user's thumb
rather than snapping when they let go:
<Box scale:back={[1, 0.95]} opacity:back={[1, 0.8]}>
<Content />
</Box> The first value is where the property sits at rest, the second where it lands at a completed back gesture. Everything in between follows the drag.
Predictive back
Android's system-level back preview — where the outgoing screen shrinks to show what is behind it — is off unless you ask for it:
# whitehall.toml
[app]
predictive_back = true That adds android:enableOnBackInvokedCallback="true" to the manifest. See Configuration.
See Also
- $navigate — moving between screens
- $onMount / $onDispose — composition, not navigation
- $scroll / $drag — the driver system
$backbelongs to - Transitions — screen enter and exit animations