$onMount / $onDispose
React to a screen or component appearing and leaving.
$onMount
Runs each time the screen or component appears. It is a coroutine — it calls a suspend function or $fetch directly, no wrapper needed:
var data: List<Show>? = $state(null)
$onMount {
data = $fetch("https://api.example.com/shows")
}
@if (data != null) {
<LazyColumn>
@for (show in data) {
<Text>{show.name}</Text>
}
</LazyColumn>
} else {
<CircularProgressIndicator />
} $onDispose
Runs each time the screen or component leaves:
var connection: WebSocket? = $state(null)
$onMount {
connection = WebSocket("wss://api.example.com")
connection?.connect()
}
$onDispose {
connection?.close()
} See it happen
Add $onDispose and watch the disposal code appear beside the mount code.
var connected = $state(false)
$onMount {
connected = true
}
<Text>{connected}</Text>$onMount runs when the screen or component appears. It is a coroutine, so it can call a suspend function or $fetch directly.
Navigating away and back
Navigating away and back runs both hooks again, so a screen that loads in $onMount reloads when you return to it. On a screen, a rotation is not a
visit and runs neither. A component is composed afresh by a rotation, so its hooks run
again.
A screen's $state is not reset between visits — $onMount finds
it as the last visit left it. Seed state in the declaration, not in the hook, if it should
happen once:
var items = $state(listOf<Item>()) // seeded once, here
$onMount {
if (items.isEmpty()) items = loadItems()
} Multiple hooks
A screen or component can have more than one of each:
var data: String? = $state(null)
$onMount {
println("mounted")
}
$onMount {
data = "loaded"
}
$onDispose {
println("disposed")
} Common Patterns
Data Loading
var posts: List<Post> = $state(listOf())
var error: String? = $state(null)
$onMount {
try {
posts = $fetch("https://api.example.com/posts")
} catch (e: Exception) {
error = e.message
}
} Polling
var status = $state("unknown")
$onMount {
while (true) {
status = $fetch("https://api.example.com/status")
delay(5000)
}
}
$onDispose {
// the loop's coroutine is cancelled with it
} Event Listeners
var batteryLevel = $state(100)
var receiver: BatteryReceiver? = null
$onMount {
receiver = BatteryReceiver { level -> batteryLevel = level }
registerReceiver(receiver)
}
$onDispose {
unregisterReceiver(receiver)
} Reacting to a change
$onMount runs once per visit. For code that should re-run whenever a value
changes, use $effect instead:
var searchQuery = $state("")
var results = $state(listOf<Result>())
$effect {
val q = searchQuery
results = if (q.isEmpty()) listOf() else $fetch("https://api.example.com/search?q=$q")
}
<TextField bind:value={searchQuery} /> The block's first line reads searchQuery, so the effect reruns whenever it
changes — there is no dependency list to declare or keep in sync.
For data loading that runs before a screen renders, use a screen loader instead of $onMount. A loader gets a loading state and an +error.wh for free.
See Also
- $data - Data loading patterns
- $fetch - HTTP requests
- $dispatch / $periodic - Background work
- Coroutines - suspend functions and dispatchers