Documentation

$onMount / $onDispose

React to component lifecycle events.

$onMount

Run code when a component mounts:

var data: List<Show>? = 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

Clean up resources when component is removed:

var connection: WebSocket? = null

$onMount {
  connection = WebSocket("wss://api.example.com")
  connection?.connect()
}

$onDispose {
  connection?.close()
}

With Coroutines

Use launch for concurrent operations:

var shows: List<Show> = []
var isLoading = true

$onMount {
  launch {
    shows = $fetch("https://api.example.com/shows")
    isLoading = false
  }
}

Multiple Effects

You can have multiple lifecycle hooks:

$onMount {
  println("Component mounted")
}

$onMount {
  launch {
    data = loadData()
  }
}

$onDispose {
  println("Component disposed")
}

Dependencies

Currently, $onMount runs once when the component mounts. For reactive effects based on state changes, use ViewModels or derived state:

var searchQuery = ""
var results: List<Result> = []

// This approach re-runs automatically when searchQuery changes
suspend fun search() {
  if (searchQuery.isEmpty()) {
    results = []
    return
  }
  results = $fetch("https://api.example.com/search?q=$searchQuery")
}

// Call on state change
<TextField
  bind:value={searchQuery}
  onChange={() => launch { search() }}
/>

Common Patterns

Data Loading

var posts: List<Post> = []
var error: String? = null

$onMount {
  launch {
    try {
      posts = $fetch("https://api.example.com/posts")
    } catch (e: Exception) {
      error = e.message
    }
  }
}

Polling

var status: String = "unknown"

$onMount {
  launch {
    while (true) {
      status = $fetch("https://api.example.com/status")
      delay(5000) // Poll every 5 seconds
    }
  }
}

$onDispose {
  // Coroutine is automatically cancelled
}

Event Listeners

var batteryLevel = 100

$onMount {
  val receiver = BatteryReceiver { level ->
    batteryLevel = level
  }
  registerReceiver(receiver)
}

$onDispose {
  unregisterReceiver(receiver)
}

Behind the Scenes

Whitehall transpiles lifecycle hooks to Compose APIs:

  • $onMountLaunchedEffect(Unit) { ... }
  • $onDisposeDisposableEffect(Unit) { onDispose { ... } }

For data loading that runs before screen render, use screen loaders instead of $onMount. Loaders provide better UX with automatic loading states.

Dispatchers

Run code on specific threads:

$onMount {
  io {
    // IO-bound work (network, disk)
    val data = loadFromDatabase()
  }

  cpu {
    // CPU-intensive work
    val processed = processLargeDataset()
  }

  main {
    // Update UI
    updateState()
  }
}

See Also