CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-compose-runtime--runtime-desktop

Tree composition support and runtime infrastructure for Compose Multiplatform desktop applications with declarative UI development APIs.

Pending
Overview
Eval results
Files

effects.mddocs/

Effects and Lifecycle

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.

Capabilities

Side Effects

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")
    }
}

Launched Effects

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)
}

Disposable Effects

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 DisposableEffectResult

Usage 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)
        }
    }
}

Coroutine Scope Management

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(): CoroutineScope

Usage 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")
            }
        }
    }
}

State Production from Async Sources

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)
    }
}

Flow Integration

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)
        }
    }
}

Advanced Effect Patterns

Effect Cleanup Patterns

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()
        }
    }
}

Conditional Effects

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

docs

collections.md

composition.md

effects.md

index.md

state-management.md

tile.json