# Kotlin Data Kmp Data Layer

> Use when implementing or reviewing KMP data layers, including repositories, data sources, source-of-truth design, API exposure, conflict resolution, error handling, and main-safe data operations.

- Skill: `mmiani/kotlin-data-kmp-data-layer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mmiani/kotlin-data-kmp-data-layer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mmiani/kotlin-data-kmp-data-layer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: Apache-2.0
- Author: mmiani (https://skillmd.com/u/mmiani)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/mmiani/kotlin-data-kmp-data-layer

---


# Kotlin Multiplatform Data Layer

Use this skill when designing, implementing, or reviewing the data layer in a Kotlin Multiplatform project.

This skill is intentionally strict. Its purpose is to keep repositories meaningful, data ownership explicit, source-of-truth decisions clear, and upper layers insulated from transport and persistence details.

## Primary goals

The data-layer design should optimize for:

- clear ownership of application data
- a single source of truth per repository
- repositories as the entry points to data access
- clean separation between repositories and data sources
- conflict resolution across multiple sources in one place
- API shapes that fit Kotlin best practices
- immutable exposed data
- main-safe repository and data-source APIs
- predictable failure handling
- testable mapping and coordination logic

---

## Official defaults to prefer

Unless the project has a strong reason not to, prefer these defaults:

- the data layer contains application data and business logic
- repositories expose data to the rest of the app
- repositories centralize changes to data
- repositories resolve conflicts between multiple data sources
- repositories abstract data sources from the rest of the app
- repositories act as the entry points to the data layer
- each data source is responsible for one source only
- each repository defines a single source of truth
- one-shot operations use `suspend` functions
- updates over time use `Flow`
- exposed data is immutable
- repository and data-source APIs are main-safe

---

## Review and implementation dimensions

### 1. Data-layer responsibility

The data layer is not only a transport layer.

Check whether:
- the data layer owns application data concerns
- business logic that belongs with data ownership is located here
- upper layers are protected from storage and transport details

Flag as a concern when:
- the data layer is treated as only an HTTP wrapper
- business rules around creation, storage, reconciliation, or change tracking leak upward
- repositories are too thin to add meaningful ownership or coordination

### 2. Repository responsibility

Repositories should be meaningful architectural boundaries.

Check whether each repository is responsible for:
- exposing data to the rest of the app
- centralizing changes to that data
- resolving conflicts between multiple sources
- abstracting data sources from callers
- containing business logic related to data ownership and coordination

Flag as a concern when:
- repositories merely mirror endpoint methods
- repositories expose transport-layer details directly
- upper layers coordinate local and remote sources themselves
- conflict resolution is duplicated outside the repository

### 3. Data-source responsibility

Each data source should work with exactly one source of data.

Examples:
- a network source
- a database source
- a file source
- an in-memory source

Check whether:
- each data source has a narrow responsibility
- repositories depend on data sources
- other layers do not depend on data sources directly

Flag as a concern when:
- ViewModels, presenters, use cases, or UI call data sources directly
- one data source mixes several unrelated storage/transport concerns
- repositories do not meaningfully sit between callers and sources

### 4. Source of truth

Each repository should define a single source of truth.

Check whether:
- the source of truth is explicit
- the data exposed from the repository comes from that source of truth
- the repository updates that source of truth consistently
- multi-source reconciliation feeds back into the source of truth

Strong candidates:
- local database
- in-memory cache for specific bounded cases

For offline-first behavior, prefer a local data source such as a database as the source of truth.

Flag as a concern when:
- multiple sources are treated as simultaneously authoritative
- UI consumes remote responses directly while local state is supposed to be canonical
- the source-of-truth choice is implicit or unstable

### 5. API shape

The data layer should expose APIs based on the kind of operation.

Prefer:
- `suspend` functions for one-shot CRUD-style operations
- `Flow` for observing changes over time

Check whether:
- one-shot work is modeled as one-shot APIs
- long-lived observation uses `Flow`
- callers receive stable APIs that match how the data behaves

Flag as a concern when:
- streaming data is exposed as repeated polling by upper layers without reason
- one-shot operations are modeled as long-lived observable streams unnecessarily
- API style is inconsistent across similar repositories

### 6. Immutability of exposed data

The data exposed by the data layer should be immutable.

Check whether:
- repositories expose immutable models or collections
- callers cannot mutate shared state directly
- data crossing layer boundaries is safe to use concurrently

Flag as a concern when:
- mutable state is exposed directly from repositories
- collections are shared in mutable form across layers
- upper layers can tamper with repository-owned state

### 7. Main-safety and threading

Repository and data-source APIs should be safe to call from the main thread.

That means these classes are responsible for shifting blocking or expensive work to the proper thread when needed.

On **Android/JVM targets**, this typically means dispatching to `Dispatchers.IO` or a similar dispatcher inside repositories and data sources.

On **Kotlin/Native targets** (iOS, macOS, etc.), the threading model changed significantly in Kotlin 1.7.20+ with the new memory model. The old frozen-object restrictions have been removed. Coroutines on Kotlin/Native now behave much more like on JVM. However, some platform SDKs (UIKit, certain iOS APIs) still require main-thread access, so threading discipline remains relevant at the platform boundary.

Check whether:
- file, database, network, or expensive filtering work is not forced onto callers
- repositories and data sources encapsulate threading concerns appropriately
- upper layers are not made responsible for knowing threading details of the data layer
- platform-specific threading requirements (e.g., main-thread-only iOS APIs) are respected at the edge, not inside shared business logic

Flag as a concern when:
- callers must manually move work off the main thread for normal repository usage
- repositories perform blocking work without appropriate dispatching
- threading policy is inconsistent or undocumented across targets
- Kotlin/Native-specific threading behavior is assumed to be the same as the old frozen-object model

### 8. Error handling model

The official guidance allows repository and data-source interactions to either succeed or throw exceptions, using Kotlin’s normal error-handling mechanisms such as `try/catch` for suspend functions and `catch` for flows.

Check whether:
- the project has a consistent error model
- failures are surfaced deliberately
- custom exceptions are used when domain-specific failure meaning matters
- result-wrapper patterns are used only when the project deliberately wants that API shape

Flag as a concern when:
- errors are swallowed
- every repository invents a different failure contract
- strings are used as the primary error model
- upper layers cannot distinguish meaningful failure cases when they need to

### 9. Naming and ownership clarity

Prefer repository names based on the data they own.

Prefer data-source names based on:
- the data they handle
- and the type of source

Check whether:
- repository names describe owned data, not transport details
- data-source names describe role and source clearly
- implementation details do not leak into architecture unnecessarily

Flag as a concern when:
- naming obscures ownership
- class names couple callers to a storage technology without reason
- repository/data-source boundaries are hard to infer from names

### 10. Upper-layer insulation

The data layer should shield the rest of the app from implementation detail.

Check whether:
- UI/state-holder/domain code depends on repository contracts rather than raw network/database details
- storage/transport migrations would stay localized inside the data layer
- data-source technology choices do not leak upward

Flag as a concern when:
- UI models mirror backend payloads directly
- persistence schemas leak into presentation
- changing a storage or network implementation would force changes across many layers

### 11. Testability

The data layer should be testable in isolation.

Check whether:
- repository coordination logic can be tested
- mappers are pure and testable
- source-of-truth behavior can be validated
- conflict-resolution logic can be exercised with fakes
- error paths are testable

Flag as a concern when:
- repository logic depends on hidden globals
- source-of-truth behavior is implicit and hard to verify
- coordination logic only works in large integration tests

---

## Required output format

When using this skill, respond with:

1. **Data-layer summary**
   - repository structure
   - data-source structure
   - source-of-truth design
   - API shape

2. **What is structurally sound**
   - concrete strengths only

3. **Issues by dimension**
   - data-layer responsibility
   - repository responsibility
   - data-source responsibility
   - source of truth
   - API shape
   - immutability
   - main-safety
   - error handling
   - naming/ownership clarity
   - upper-layer insulation
   - testability

4. **Severity for each issue**
   - high / medium / low

5. **Concrete recommendations**
   - exact repository/data-source restructuring steps
   - source-of-truth corrections
   - API-shape corrections
   - threading/error-model corrections

6. **Suggested target structure**
   - proposed repository/data-source layout if useful

7. **Open risks**
   - migration cost
   - compatibility concerns
   - remaining architectural ambiguity

---

## Anti-patterns to flag aggressively

- repositories that only mirror endpoints
- upper layers depending directly on data sources
- no explicit single source of truth
- multiple writable authorities for the same data
- mutable data exposed from repositories
- blocking or expensive work forced onto callers
- transport or persistence details leaking into UI/state holders
- silent error swallowing
- inconsistent API shapes for similar operations

---

## References

- Android data layer: https://developer.android.com/topic/architecture/data-layer
- Android architecture recommendations: https://developer.android.com/topic/architecture/recommendations
- Kotlin Multiplatform project structure: https://kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html
- Kotlin coroutines: https://kotlinlang.org/docs/coroutines-overview.html
