Tree composition support and runtime infrastructure for Compose Multiplatform desktop applications with declarative UI development APIs.
—
Side effect APIs for integrating with external systems, managing resources, and handling component lifecycle in Compose. Effects allow you to perform operations that go beyond the pure functional nature of composables.
Execute code that runs on every recomposition.
/**
* Executes a side effect on every recomposition
* Use sparingly as it runs on every recomposition
* @param effect The side effect to execute
*/
@Composable
fun SideEffect(effect: () -> Unit)Usage Examples:
import androidx.compose.runtime.*
import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope
@Composable
fun SideEffectExample(analytics: Analytics) {
val screenName = "UserProfile"
// Runs on every recomposition
SideEffect {
analytics.trackScreenView(screenName)
}
Text("User Profile Screen")
}
@Composable
fun LoggingExample() {
var count by remember { mutableStateOf(0) }
// Logs every time count changes and composable recomposes
SideEffect {
println("Recomposed with count: $count")
}
Button(onClick = { count++ }) {
Text("Count: $count")
}
}Launch coroutines that are tied to the composable's lifecycle.
/**
* Launches a coroutine in the composition scope
* Coroutine is cancelled when the composable leaves composition or keys change
* @param keys Dependency keys - effect restarts when any key changes
* @param block Suspending block to execute
*/
@Composable
fun LaunchedEffect(
vararg keys: Any?,
block: suspend CoroutineScope.() -> Unit
)Usage Examples:
@Composable
fun LaunchedEffectExample(userId: String) {
var userData by remember { mutableStateOf<UserData?>(null) }
// Launch effect that restarts when userId changes
LaunchedEffect(userId) {
userData = null // Show loading
try {
userData = userRepository.getUser(userId)
} catch (e: Exception) {
userData = UserData.Error(e.message)
}
}
when (val data = userData) {
null -> CircularProgressIndicator()
is UserData.Success -> UserProfile(data.user)
is UserData.Error -> ErrorMessage(data.message)
}
}
@Composable
fun AnimationEffect() {
var animationState by remember { mutableStateOf(0f) }
// Animate continuously
LaunchedEffect(Unit) {
while (true) {
animationState = (animationState + 0.01f) % 1f
delay(16) // ~60 FPS
}
}
CustomAnimatedComponent(progress = animationState)
}Effects with cleanup capabilities when the composable leaves composition or keys change.
/**
* Effect that requires cleanup when keys change or component leaves composition
* @param keys Dependency keys - effect restarts when any key changes
* @param effect Effect block that returns a DisposableEffectResult
*/
@Composable
fun DisposableEffect(
vararg keys: Any?,
effect: DisposableEffectScope.() -> DisposableEffectResult
)
/**
* Scope for disposable effects
*/
interface DisposableEffectScope {
fun onDispose(onDisposeEffect: () -> Unit): DisposableEffectResult
}
/**
* Result of a disposable effect providing cleanup callback
*/
interface DisposableEffectResultUsage Examples:
@Composable
fun DisposableEffectExample() {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> println("Component started")
Lifecycle.Event.ON_STOP -> println("Component stopped")
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
@Composable
fun KeyboardListenerExample() {
DisposableEffect(Unit) {
val keyListener = object : KeyListener {
override fun keyPressed(e: KeyEvent) {
println("Key pressed: ${e.keyCode}")
}
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventDispatcher(keyListener)
onDispose {
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.removeKeyEventDispatcher(keyListener)
}
}
}Get a coroutine scope that survives recompositions for launching coroutines from event handlers.
/**
* Returns a CoroutineScope that is cancelled when the composable leaves composition
* Use this for launching coroutines from event handlers like onClick
* @return CoroutineScope tied to the composable's lifecycle
*/
@Composable
fun rememberCoroutineScope(): CoroutineScopeUsage Examples:
@Composable
fun CoroutineScopeExample() {
val scope = rememberCoroutineScope()
var result by remember { mutableStateOf("") }
Button(
onClick = {
// Launch coroutine from event handler
scope.launch {
result = "Loading..."
delay(2000)
result = apiService.fetchData()
}
}
) {
Text("Fetch Data")
}
Text("Result: $result")
}
@Composable
fun ScrollToTopExample() {
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
Column {
Button(
onClick = {
scope.launch {
listState.animateScrollToItem(0)
}
}
) {
Text("Scroll to Top")
}
LazyColumn(state = listState) {
items(100) { index ->
Text("Item $index")
}
}
}
}Create state from asynchronous data sources.
/**
* Produces state from a suspending producer function
* @param initialValue Initial value while producer is running
* @param keys Dependency keys - producer restarts when any key changes
* @param producer Suspending function that produces values
* @return State holding the produced value
*/
@Composable
fun <T> produceState(
initialValue: T,
vararg keys: Any?,
producer: suspend ProduceStateScope<T>.() -> Unit
): State<T>
/**
* Scope for producing state values
*/
interface ProduceStateScope<T> : MutableState<T>, CoroutineScope {
suspend fun awaitDispose(onDispose: () -> Unit)
}Usage Examples:
@Composable
fun ProduceStateExample(url: String) {
val imageState by produceState<ImageBitmap?>(null, url) {
value = loadImageFromNetwork(url)
}
when (val image = imageState) {
null -> CircularProgressIndicator()
else -> Image(bitmap = image, contentDescription = null)
}
}
@Composable
fun NetworkDataExample(endpoint: String) {
val networkData by produceState(
initialValue = NetworkState.Loading,
key1 = endpoint
) {
try {
val data = httpClient.get(endpoint)
value = NetworkState.Success(data)
} catch (e: Exception) {
value = NetworkState.Error(e.message ?: "Unknown error")
}
awaitDispose {
// Cleanup network resources
httpClient.cancel()
}
}
when (networkData) {
is NetworkState.Loading -> LoadingIndicator()
is NetworkState.Success -> DataDisplay(networkData.data)
is NetworkState.Error -> ErrorMessage(networkData.message)
}
}Convert Flow/StateFlow to Compose State.
/**
* Collects values from a Flow and represents them as State
* @param initial Initial value to use before first emission
* @param context CoroutineContext for collection (default: EmptyCoroutineContext)
* @return State that updates with Flow emissions
*/
@Composable
fun <T> Flow<T>.collectAsState(
initial: T,
context: CoroutineContext = EmptyCoroutineContext
): State<T>
/**
* Collects values from a StateFlow and represents them as State
* Uses the StateFlow's current value as initial value
*/
@Composable
fun <T> StateFlow<T>.collectAsState(
context: CoroutineContext = EmptyCoroutineContext
): State<T>Usage Examples:
@Composable
fun FlowExample(viewModel: MyViewModel) {
// Collect from Flow
val uiState by viewModel.uiStateFlow.collectAsState()
// Collect from Flow with initial value
val searchResults by viewModel.searchFlow.collectAsState(
initial = emptyList()
)
// Collect with custom context
val backgroundData by viewModel.backgroundFlow.collectAsState(
initial = null,
context = Dispatchers.IO
)
when (uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> SuccessScreen(uiState.data)
is UiState.Error -> ErrorScreen(uiState.message)
}
}
@Composable
fun DatabaseExample(database: UserDatabase) {
val users by database.getAllUsers().collectAsState(initial = emptyList())
LazyColumn {
items(users) { user ->
UserItem(user = user)
}
}
}Common patterns for cleaning up resources in effects.
@Composable
fun ResourceManagementExample() {
DisposableEffect(Unit) {
val resource = acquireResource()
onDispose {
resource.release()
}
}
LaunchedEffect(Unit) {
val connection = openConnection()
try {
// Use connection
handleConnection(connection)
} finally {
connection.close()
}
}
}Effects that run conditionally based on state.
@Composable
fun ConditionalEffectExample() {
var isActive by remember { mutableStateOf(false) }
if (isActive) {
LaunchedEffect(Unit) {
startPeriodicUpdate()
}
}
// Alternative pattern
LaunchedEffect(isActive) {
if (isActive) {
startService()
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-runtime--runtime-desktop