CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Compose Multiplatform runtime library for iOS UIKit x64 target providing core runtime functionality for declarative UI framework integration.

Pending
Overview
Eval results
Files

effects.mddocs/

Effects

Side effect management with lifecycle-aware execution and automatic cleanup. Effects provide safe ways to perform operations outside the composition scope while integrating properly with the Compose lifecycle.

Capabilities

DisposableEffect

Effect that requires cleanup when the effect leaves the composition or its keys change.

/**
 * Effect that runs when keys change and provides cleanup via DisposableEffectResult
 * @param key1 Key that determines when effect should restart
 * @param effect Lambda that runs the effect and returns cleanup
 */
@Composable
fun DisposableEffect(
    key1: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
): Unit

/**
 * DisposableEffect with two key dependencies
 * @param key1 First key dependency
 * @param key2 Second key dependency
 * @param effect Lambda that runs the effect and returns cleanup
 */
@Composable
fun DisposableEffect(
    key1: Any?,
    key2: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
): Unit

/**
 * DisposableEffect with three key dependencies
 * @param key1 First key dependency
 * @param key2 Second key dependency
 * @param key3 Third key dependency
 * @param effect Lambda that runs the effect and returns cleanup
 */
@Composable
fun DisposableEffect(
    key1: Any?,
    key2: Any?,
    key3: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
): Unit

/**
 * DisposableEffect with multiple key dependencies
 * @param keys Variable number of key dependencies
 * @param effect Lambda that runs the effect and returns cleanup
 */
@Composable
fun DisposableEffect(
    vararg keys: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
): Unit

/**
 * Scope for DisposableEffect providing cleanup mechanism
 */
interface DisposableEffectScope {
    /**
     * Creates a cleanup handler
     * @param onDispose Function called when effect is disposed
     * @return DisposableEffectResult for the effect
     */
    fun onDispose(onDispose: () -> Unit): DisposableEffectResult
}

/**
 * Result of a DisposableEffect containing cleanup logic
 */
interface DisposableEffectResult

Usage Examples:

@Composable
fun DisposableEffectExample(userId: String) {
    // Effect that depends on userId
    DisposableEffect(userId) {
        val subscription = userService.subscribeToUser(userId) { user ->
            // Handle user updates
        }
        
        // Cleanup when userId changes or composable leaves composition
        onDispose {
            subscription.cancel()
        }
    }
    
    // iOS-specific example: Observer pattern
    DisposableEffect(Unit) {
        val observer = NotificationCenter.default.addObserver(
            forName: UIApplication.didBecomeActiveNotification,
            object: null,
            queue: OperationQueue.main
        ) { _ ->
            // Handle app becoming active
        }
        
        onDispose {
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

LaunchedEffect

Effect that launches a coroutine and automatically cancels it when the effect leaves the composition.

/**
 * Launches a coroutine in the composition scope
 * @param key1 Key that determines when to restart the coroutine
 * @param block Suspending function to execute in the coroutine
 */
@Composable
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
): Unit

/**
 * LaunchedEffect with two key dependencies
 * @param key1 First key dependency
 * @param key2 Second key dependency
 * @param block Suspending function to execute in the coroutine
 */
@Composable
fun LaunchedEffect(
    key1: Any?,
    key2: Any?,
    block: suspend CoroutineScope.() -> Unit
): Unit

/**
 * LaunchedEffect with three key dependencies
 * @param key1 First key dependency
 * @param key2 Second key dependency
 * @param key3 Third key dependency
 * @param block Suspending function to execute in the coroutine
 */
@Composable
fun LaunchedEffect(
    key1: Any?,
    key2: Any?,
    key3: Any?,
    block: suspend CoroutineScope.() -> Unit
): Unit

/**
 * LaunchedEffect with multiple key dependencies
 * @param keys Variable number of key dependencies
 * @param block Suspending function to execute in the coroutine
 */
@Composable
fun LaunchedEffect(
    vararg keys: Any?,
    block: suspend CoroutineScope.() -> Unit
): Unit

Usage Examples:

@Composable
fun LaunchedEffectExample(searchQuery: String) {
    var searchResults by remember { mutableStateOf<List<SearchResult>>(emptyList()) }
    
    // Launch search when query changes
    LaunchedEffect(searchQuery) {
        if (searchQuery.isNotEmpty()) {
            delay(300) // Debounce
            try {
                searchResults = searchService.search(searchQuery)
            } catch (e: Exception) {
                // Handle error
            }
        }
    }
    
    // Launch periodic update
    LaunchedEffect(Unit) {
        while (true) {
            delay(30_000) // 30 seconds
            refreshData()
        }
    }
}

@Composable
fun NetworkStatusExample() {
    var isOnline by remember { mutableStateOf(true) }
    
    LaunchedEffect(Unit) {
        networkMonitor.status.collect { status ->
            isOnline = status == NetworkStatus.Connected
        }
    }
}

SideEffect

Effect that executes after every successful recomposition.

/**
 * Executes a side effect after every recomposition
 * @param effect Function to execute after recomposition
 */
@Composable
fun SideEffect(effect: () -> Unit): Unit

Usage Examples:

@Composable
fun SideEffectExample(user: User) {
    // Log every time this composable recomposes
    SideEffect {
        logger.log("UserProfile recomposed for user: ${user.id}")
    }
    
    // Update analytics after recomposition
    SideEffect {
        analytics.track("profile_viewed", mapOf("user_id" to user.id))
    }
}

@Composable
fun UIKitIntegrationExample() {
    val currentUser by viewModel.currentUser.collectAsState()
    
    // Update UIKit view after recomposition
    SideEffect {
        navigationController.title = currentUser?.name ?: "Profile"
    }
}

rememberCoroutineScope

Returns a CoroutineScope that's automatically cancelled when the composition leaves.

/**
 * Returns a CoroutineScope bound to the composition lifecycle
 * @return CoroutineScope that's cancelled when composition is disposed
 */
@Composable
fun rememberCoroutineScope(): CoroutineScope

/**
 * Returns a CoroutineScope bound to the composition lifecycle with custom context
 * @param getContext Function to provide custom coroutine context
 * @return CoroutineScope with custom context that's cancelled when composition is disposed
 */
@Composable
fun rememberCoroutineScope(getContext: () -> CoroutineContext): CoroutineScope

Usage Examples:

@Composable
fun CoroutineScopeExample() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }
    
    Column {
        Button(onClick = {
            // Launch coroutine in response to user action
            scope.launch {
                try {
                    val result = performNetworkCall()
                    snackbarHostState.showSnackbar("Success: $result")
                } catch (e: Exception) {
                    snackbarHostState.showSnackbar("Error: ${e.message}")
                }
            }
        }) {
            Text("Perform Action")
        }
        
        SnackbarHost(hostState = snackbarHostState)
    }
}

@Composable
fun CustomCoroutineScopeExample() {
    // Custom scope with IO dispatcher for background work
    val ioScope = rememberCoroutineScope { Dispatchers.IO }
    
    // Custom scope for iOS main thread operations
    val mainScope = rememberCoroutineScope { IOSMainDispatcher }
    
    Button(onClick = {
        // Heavy computation on IO dispatcher
        ioScope.launch {
            val data = performHeavyComputation()
            
            // Switch to main thread for UI updates
            mainScope.launch {
                updateUI(data)
            }
        }
    }) {
        Text("Process Data")
    }
}

rememberUpdatedState

Remembers a value that's always up-to-date in long-running effects.

/**
 * Remembers a value that's always current in long-running operations
 * @param newValue The current value to remember
 * @return State containing the most recent value
 */
@Composable
fun <T> rememberUpdatedState(newValue: T): State<T>

Usage Examples:

@Composable
fun TimerExample(onTimeout: () -> Unit) {
    // Remember the latest callback to avoid stale captures
    val currentOnTimeout by rememberUpdatedState(onTimeout)
    
    LaunchedEffect(Unit) {
        delay(5000) // 5 second timer
        currentOnTimeout() // This will call the latest onTimeout
    }
}

@Composable
fun IntervalExample(interval: Long, action: () -> Unit) {
    val currentAction by rememberUpdatedState(action)
    
    LaunchedEffect(interval) {
        while (true) {
            delay(interval)
            currentAction() // Always uses the latest action
        }
    }
}

Advanced Effect Patterns

Effect Error Handling

@Composable
fun ErrorHandlingExample() {
    var error by remember { mutableStateOf<String?>(null) }
    
    LaunchedEffect(Unit) {
        try {
            performRiskyOperation()
        } catch (e: Exception) {
            error = e.message
        }
    }
    
    error?.let { errorMessage ->
        Text("Error: $errorMessage", color = Color.Red)
    }
}

Effect Cancellation

@Composable
fun CancellationExample(shouldRun: Boolean) {
    LaunchedEffect(shouldRun) {
        if (shouldRun) {
            try {
                while (true) {
                    performPeriodicTask()
                    delay(1000)
                }
            } catch (e: CancellationException) {
                // Cleanup on cancellation
                cleanup()
                throw e // Re-throw to properly handle cancellation
            }
        }
    }
}

Complex Effect Dependencies

@Composable
fun ComplexDependenciesExample(
    userId: String,
    refreshTrigger: Int,
    settings: UserSettings
) {
    // Effect runs when any dependency changes
    LaunchedEffect(userId, refreshTrigger, settings.theme) {
        loadUserData(userId, settings)
    }
}

iOS Platform Considerations

Main Thread Integration

@Composable
fun IOSMainThreadExample() {
    LaunchedEffect(Unit) {
        // Switch to main dispatcher for UI updates
        withContext(Dispatchers.Main.immediate) {
            updateUIKitView()
        }
    }
}

Background Task Management

@Composable
fun BackgroundTaskExample() {
    DisposableEffect(Unit) {
        val taskId = UIApplication.shared.beginBackgroundTask {
            // Background task expired
        }
        
        onDispose {
            UIApplication.shared.endBackgroundTask(taskId)
        }
    }
}

Performance Considerations

  • Effect Keys: Use appropriate keys to control when effects restart
  • Cancellation: Always handle coroutine cancellation properly in LaunchedEffect
  • Resource Cleanup: Use DisposableEffect for resources that need explicit cleanup
  • State Updates: Use rememberUpdatedState for callbacks in long-running effects
  • Thread Safety: Be mindful of thread safety when accessing mutable state from effects

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-compose-runtime--runtime-uikitx64

docs

composition-lifecycle.md

composition-local.md

effects.md

index.md

ios-integration.md

snapshot-system.md

state-management.md

tile.json