This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: cmp-retained-state-cross-platform-lifecycle-contract

Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract

Enforces deterministic state restoration and native bridge lifecycle synchronization between Android ViewModel/SavedState semantics and iOS UIViewController/Jetsam lifecycles. Prevents silent state loss on Android OS process recreation and eliminates retain cycles between Kotlin/Native GC and Swift ARC under extreme memory pressure.

Prohibited Anti-Pattern: Retaining In-Memory Compose State Without SavedState Serialization and Leaking Kotlin-Darwin Native Bridges Across View Controller Eviction

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract - Enforces deterministic state restoration and native bridge lifecycle synchronization between Android ViewModel/SavedState semantics and iOS UIViewController/Jetsam lifecycles. Prevents silent state loss on Android OS process recreation and eliminates retain cycles between Kotlin/Native GC and Swift ARC under extreme memory pressure.
globs: **/commonMain/**/presentation/**/*.kt, **/commonMain/**/viewmodel/**/*.kt, **/iosMain/**/lifecycle/**/*.kt, **/iosMain/**/viewcontroller/**/*.kt, iosApp/**/*.swift
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/cmp-retained-state-cross-platform-lifecycle-contract"
---

# Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Retaining In-Memory Compose State Without SavedState Serialization and Leaking Kotlin-Darwin Native Bridges Across View Controller Eviction

## System Constraints
You are a Principal Mobile Architect enforcing absolute architectural contracts across Compose Multiplatform (CMP) Android and iOS targets. You mandate deterministic state survival across Android low-memory process kills, safe Swift/UIKit bridge detachment upon UIViewController deallocation, and explicit bi-directional disposal between ViewModelStoreOwner scopes and native iOS lifecycle events.

## Strict Guidelines
- Engineers MUST ALWAYS serialize critical UI state using `rememberSaveable` with a strict `Saver` or `SavedStateHandle` with `kotlinx.serialization` to survive Android process kills and iOS background eviction.
- Engineers MUST NOT capture strong references to native iOS platform objects (such as `UIViewController`, `UIView`, or Darwin delegates) inside Kotlin retained `ViewModel` instances or long-lived coroutine scopes.
- Native iOS bridge adapters MUST implement explicit teardown contracts triggered by `UIViewController.viewDidDisappear` or `deinit` to cancel active coroutine scopes and detach Kotlin/Native root references.
- Shared ViewModels bridging to iOS MUST NOT rely exclusively on platform garbage collection; they MUST expose an explicit `clear()` or `dispose()` contract bound to the iOS navigation coordinator.
- Components NEVER store unsaved business-critical transactional state purely in Compose `mutableStateOf` without a persistent fallback or saved state restorer.
- State restoration logic MUST be verified against both Android Activity recreation (`Don't keep activities` / `am kill`) and iOS memory pressure simulation (`didReceiveMemoryWarning` / task suspension).

## Bad vs Good Implementations
### ViewModel State Preservation & Native Bridge Lifecycle Bridge

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Fails to survive Android OS process kill because uiState is not backed by SavedStateHandle or Saver. Holds a strong reference to the native iOS view layer, preventing Swift ARC deallocation and Kotlin GC cycle resolution when the ViewController is popped, leading to persistent leaks under iOS memory pressure.
```kotlin
// Anti-pattern: State is ephemeral in memory; strong bridge captures UIViewController directly.
class BadProfileViewModel(
    private val uiViewController: Any? // Holds direct strong ref to iOS ViewController
) {
    // Ephemeral state lost on process death
    var uiState by mutableStateOf(ProfileUiState())
        private set

    init {
        // Unbounded coroutine scope without disposal contract for iOS
        CoroutineScope(Dispatchers.Main).launch {
            NativeNotificationCenter.registerObserver { data ->
                // Leaks observer and keeps ViewModel pinned in memory forever
                uiState = uiState.copy(notificationsCount = data.count)
            }
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Preserves state across Android process death and iOS recreation via deterministic JSON serialization in SavedStateHandle. The native bridge is encapsulated behind a strict detachment interface, cleanly releasing native listeners and coroutines inside onCleared() without retaining platform UI contexts.
```kotlin
// Production standard: Serializable state via SavedStateHandle and explicit native lifecycle bridge
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class ProfileUiState(val userId: String = "", val notificationsCount: Int = 0)

interface NativeBridgeSubscription {
    fun detach()
}

class ResilientProfileViewModel(
    private val savedStateHandle: SavedStateHandle,
    private val bridgeFactory: (onUpdate: (Int) -> Unit) -> NativeBridgeSubscription
) : ViewModel() {
    private val stateKey = "profile_state_bundle_key"

    private val _uiState = MutableStateFlow(
        savedStateHandle.get<String>(stateKey)?.let {
            runCatching { Json.decodeFromString<ProfileUiState>(it) }.getOrNull()
        } ?: ProfileUiState()
    )
    val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow()

    private var nativeSubscription: NativeBridgeSubscription? = null

    init {
        nativeSubscription = bridgeFactory { count ->
            _uiState.update { it.copy(notificationsCount = count) }
            savedStateHandle[stateKey] = Json.encodeToString(ProfileUiState.serializer(), _uiState.value)
        }
    }

    override fun onCleared() {
        super.onCleared()
        nativeSubscription?.detach()
        nativeSubscription = null
    }
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract

Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract defines an industrial-grade behavioral standard for mobile engineering. Enforces deterministic state restoration and native bridge lifecycle synchronization between Android ViewModel/SavedState semantics and iOS UIViewController/Jetsam lifecycles. Prevents silent state loss on Android OS process recreation and eliminates retain cycles between Kotlin/Native GC and Swift ARC under extreme memory pressure. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Retaining In-Memory Compose State Without SavedState Serialization and Leaking Kotlin-Darwin Native Bridges Across View Controller Eviction Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftRetaining In-Memory Compose State Without SavedState Serialization and Leaking Kotlin-Darwin Native Bridges Across View Controller EvictionCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS serialize critical UI state using `rememberSaveable` with a strict `Saver` or `SavedStateHandle` with `kotlinx.serialization` to survive Android process kills and iOS background eviction.

Engineers MUST ALWAYS serialize critical UI state using `rememberSaveable` with a strict `Saver` or `SavedStateHandle` with `kotlinx.serialization` to survive Android process kills and iOS background eviction.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT capture strong references to native iOS platform objects (such as `UIViewController`, `UIView`, or Darwin delegates) inside Kotlin retained `ViewModel` instances or long-lived coroutine scopes.

Engineers MUST NOT capture strong references to native iOS platform objects (such as `UIViewController`, `UIView`, or Darwin delegates) inside Kotlin retained `ViewModel` instances or long-lived coroutine scopes.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Native iOS bridge adapters MUST implement explicit teardown contracts triggered by `UIViewController.viewDidDisappear` or `deinit` to cancel active coroutine scopes and detach Kotlin/Native root references.

Native iOS bridge adapters MUST implement explicit teardown contracts triggered by `UIViewController.viewDidDisappear` or `deinit` to cancel active coroutine scopes and detach Kotlin/Native root references.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Shared ViewModels bridging to iOS MUST NOT rely exclusively on platform garbage collection

Shared ViewModels bridging to iOS MUST NOT rely exclusively on platform garbage collection; they MUST expose an explicit `clear()` or `dispose()` contract bound to the iOS navigation coordinator.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Components NEVER store unsaved business-critical transactional state purely in Compose `mutableStateOf` without a persistent fallback or saved state restorer.

Components NEVER store unsaved business-critical transactional state purely in Compose `mutableStateOf` without a persistent fallback or saved state restorer.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
6

State restoration logic MUST be verified against both Android Activity recreation (`Don't keep activities` / `am kill`) and iOS memory pressure simulation (`didReceiveMemoryWarning` / task suspension).

State restoration logic MUST be verified against both Android Activity recreation (`Don't keep activities` / `am kill`) and iOS memory pressure simulation (`didReceiveMemoryWarning` / task suspension).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Compose Multiplatform Retained State & Cross-Process Lifecycle Eviction Contract critical for AI coding assistants?

Enforces deterministic state restoration and native bridge lifecycle synchronization between Android ViewModel/SavedState semantics and iOS UIViewController/Jetsam lifecycles. Prevents silent state loss on Android OS process recreation and eliminates retain cycles between Kotlin/Native GC and Swift ARC under extreme memory pressure. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Retaining In-Memory Compose State Without SavedState Serialization and Leaking Kotlin-Darwin Native Bridges Across View Controller Eviction) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

Compose