This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-darwin-stateflow-main-thread-affinity

KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail

Enforces strict main-thread confinement for Kotlin Multiplatform StateFlow emissions bound for Darwin targets. Prevents off-main-thread UI state bridging, race conditions, and purple runtime warnings in SwiftUI and UIKit by requiring explicit dispatcher affinity at the KMP/Native boundary.

Prohibited Anti-Pattern: Unconfined Coroutine StateFlow Bridging Directly to Darwin UI Surfaces

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail - Enforces strict main-thread confinement for Kotlin Multiplatform StateFlow emissions bound for Darwin targets. Prevents off-main-thread UI state bridging, race conditions, and purple runtime warnings in SwiftUI and UIKit by requiring explicit dispatcher affinity at the KMP/Native boundary.
globs: **/*ViewModel.kt, **/*Store.kt, **/*StateHolder.kt, **/iosMain/**/*.kt, **/darwinMain/**/*.kt, **/*View.swift, **/*ObservableModel.swift
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-darwin-stateflow-main-thread-affinity"
---

# KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Unconfined Coroutine StateFlow Bridging Directly to Darwin UI Surfaces

## System Constraints
You are a Principal Mobile Architect enforcing Kotlin Multiplatform (KMP) Shared StateFlow Darwin Main-Thread Affinity. Mandate that all reactive state streams exposed across the Kotlin/Objective-C/Swift boundary execute mutations and terminal collections strictly on Dispatchers.Main.immediate or Darwin's Main Actor to prevent UI thread corruption, frame hitches, and race conditions.

## Strict Guidelines
- Engineers MUST ALWAYS confine StateFlow emissions bridged to Darwin UI collectors to Dispatchers.Main.immediate within the shared Kotlin domain or view model layer.
- Engineers MUST NOT expose unconfined SharedFlow or StateFlow instances directly to Darwin callers without SKIE @MainActor annotation or a thread-safe Darwin main-queue adapter.
- Shared ViewModels bridging to SwiftUI MUST guarantee that value mutation triggers on iOS occur exclusively on the Main Actor to avoid CoreAnimation hitching and Thread 1 runtime assertion crashes.
- Kotlin side transformations before emission SHOULD execute on Dispatchers.Default, but the terminal StateFlow exposed across the cinterop boundary MUST switch to Dispatchers.Main.immediate via stateIn.
- SwiftUI bindings MUST NOT consume raw Kotlin Flows inside detached Tasks or background Swift Concurrency Tasks; collectors must be isolated using @MainActor or .task { @MainActor in }.

## Bad vs Good Implementations
### Kotlin StateFlow Exposure for Darwin Consumers

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Directly updating MutableStateFlow from Dispatchers.IO causes terminal collectors in Darwin (Swift/Objective-C runtime) to process emissions off the main thread, resulting in Main Thread Checker crashes and SwiftUI state corruption.
```kotlin
class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow(ProfileState.Initial)
    // CRITICAL BUG: Exposed directly while mutated from IO/Default dispatcher.
    // Consuming this in Swift bridges cross-thread mutations to UIKit/SwiftUI.
    val state: StateFlow<ProfileState> = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            val data = repository.fetchUserData()
            _state.value = ProfileState.Success(data) // Mutated on background thread
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: State mutations are dispatched explicitly onto Dispatchers.Main.immediate, ensuring that when the value bridges across the Darwin interoperability boundary, it arrives safely on the iOS main thread.
```kotlin
class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow(ProfileState.Initial)

    // Confined terminal state flow guaranteeing Darwin boundary affinity
    val state: StateFlow<ProfileState> = _state
        .flowOn(Dispatchers.Default) // Intensive computation on background pool
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = ProfileState.Initial
        )

    fun loadData() {
        viewModelScope.launch(Dispatchers.Default) {
            val data = repository.fetchUserData()
            withContext(Dispatchers.Main.immediate) {
                _state.value = ProfileState.Success(data) // Dispatched immediately to main
            }
        }
    }
}
```

### Swift Concurrency Consumption of KMP Flow

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Collecting a KMP Flow inside Task.detached executes updates outside Swift's MainActor, triggering purple Xcode runtime warnings, data races, and UI render hitches.
```swift
struct ProfileView: View {
    @StateObject private var model = ProfileStateObservable()
    
    var body: some View {
        Text(model.state.name)
            .onAppear {
                // CRITICAL BUG: Detached task collects without MainActor guarantee
                Task.detached {
                    for await state in model.viewModel.state {
                        model.state = state // Modifies published state off the main actor
                    }
                }
            }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Annotating the Swift observable model and task collector with @MainActor guarantees that all state mutations delivered across the KMP boundary occur strictly on Darwin's main thread.
```swift
@MainActor
final class ProfileStateObservable: ObservableObject {
    @Published private(set) var state: ProfileState = .initial
    private let viewModel: ProfileViewModel
    
    init(viewModel: ProfileViewModel) {
        self.viewModel = viewModel
        observeState()
    }
    
    private func observeState() {
        Task { @MainActor in
            // SKIE or KMP Darwin adapter guaranteeing MainActor isolation
            for await nextState in viewModel.state {
                self.state = nextState
            }
        }
    }
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail

KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail defines an industrial-grade behavioral standard for mobile engineering. Enforces strict main-thread confinement for Kotlin Multiplatform StateFlow emissions bound for Darwin targets. Prevents off-main-thread UI state bridging, race conditions, and purple runtime warnings in SwiftUI and UIKit by requiring explicit dispatcher affinity at the KMP/Native boundary. 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: Unconfined Coroutine StateFlow Bridging Directly to Darwin UI Surfaces 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 DriftUnconfined Coroutine StateFlow Bridging Directly to Darwin UI SurfacesCritical
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 confine StateFlow emissions bridged to Darwin UI collectors to Dispatchers.Main.immediate within the shared Kotlin domain or view model layer.

Engineers MUST ALWAYS confine StateFlow emissions bridged to Darwin UI collectors to Dispatchers.Main.immediate within the shared Kotlin domain or view model layer.

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 expose unconfined SharedFlow or StateFlow instances directly to Darwin callers without SKIE @MainActor annotation or a thread-safe Darwin main-queue adapter.

Engineers MUST NOT expose unconfined SharedFlow or StateFlow instances directly to Darwin callers without SKIE @MainActor annotation or a thread-safe Darwin main-queue adapter.

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

Shared ViewModels bridging to SwiftUI MUST guarantee that value mutation triggers on iOS occur exclusively on the Main Actor to avoid CoreAnimation hitching and Thread 1 runtime assertion crashes.

Shared ViewModels bridging to SwiftUI MUST guarantee that value mutation triggers on iOS occur exclusively on the Main Actor to avoid CoreAnimation hitching and Thread 1 runtime assertion crashes.

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

Kotlin side transformations before emission SHOULD execute on Dispatchers.Default, but the terminal StateFlow exposed across the cinterop boundary MUST switch to Dispatchers.Main.immediate via stateIn.

Kotlin side transformations before emission SHOULD execute on Dispatchers.Default, but the terminal StateFlow exposed across the cinterop boundary MUST switch to Dispatchers.Main.immediate via stateIn.

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

SwiftUI bindings MUST NOT consume raw Kotlin Flows inside detached Tasks or background Swift Concurrency Tasks

SwiftUI bindings MUST NOT consume raw Kotlin Flows inside detached Tasks or background Swift Concurrency Tasks; collectors must be isolated using @MainActor or .task { @MainActor in }.

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 KMP Shared StateFlow Darwin Main-Thread Affinity Guardrail critical for AI coding assistants?

Enforces strict main-thread confinement for Kotlin Multiplatform StateFlow emissions bound for Darwin targets. Prevents off-main-thread UI state bridging, race conditions, and purple runtime warnings in SwiftUI and UIKit by requiring explicit dispatcher affinity at the KMP/Native boundary. 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 (Unconfined Coroutine StateFlow Bridging Directly to Darwin UI Surfaces) 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

KMP