agentleFS
getdittogetditto/agent-skills/skills/kotlin-sdk/SKILL.md

kotlin-sdk

Skill4 starsChanged 9 days agoMITView on GitHub

Use ONLY for the Ditto Kotlin SDK (not Swift, Java, JS, C#, C++, Rust, Flutter or Go — each has its own skill). Triggers on .kt/.kts files, build.gradle.kts, com.ditto:ditto-kotlin or live.ditto:ditto coordinates, DittoConfig, DittoFactory.create, registerObserver, ditto.store.execute, and errors like "Query result item has already been closed.", "Unable to find binary 'libdittoall'", "Identifier conflict … FAIL conflict policy". Android and JVM targets. Ditto SDK 4.14.x, 5.0.x, 5.1.x, 5.2.x.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 6 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use the shipping API: on v5 everything is DittoFactory.create(DittoConfig), ditto.sync, ditto.store with DQL, and suspend/Flow observers.

    line 16
  2. Distinctive:

    Read only the reference sections the task needs.

    line 23
  3. Distinctive:

    Do not preload all references or the README (the capability/evidence map).

    line 24
  4. Distinctive:

    Use this skill for Kotlin Ditto code: API shape, initialization, object lifetimes, threading, error types, and build configuration.

    line 35
  5. Distinctive:

    Read sdk-5.2.md when adopting 5.2.0 features or reviewing an upgrade: encryption, store deletion, startup configuration and behavior fixes.

    line 68
  6. Distinctive:

    Read concurrency.md for the per-API table and reasoning.

    line 116
  7. Distinctive:

    Read anti-patterns.md for wrong/right forms.

    line 150

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Ditto Kotlin SDK

Use the shipping API: on v5 everything is DittoFactory.create(DittoConfig), ditto.sync, ditto.store with DQL, and suspend/Flow observers; the v4 Ditto(dependencies, identity) constructor and legacy collection API are gone. Establish the version line first (com.ditto:ditto-kotlin = v5, live.ditto:ditto = v4, Android-only); emit v5 code by default and say which version you assumed. Do not invent APIs from memory — the v4→v5 surface changed completely.

Read only the reference sections the task needs; inspect a long file's contents first. Do not preload all references or the README (the capability/evidence map).

What this skill does

Writes and reviews Ditto Kotlin code: Gradle setup, initialization, authentication, DQL store access, sync, observers and Flows, attachments, presence, transports, object lifetimes, threading, errors and testing. Supports 4.14.x (LTS), 5.0.x, 5.1.x and 5.2.x on Android (both lines) and plain JVM (5.x only).

When to use this skill

Use this skill for Kotlin Ditto code: API shape, initialization, object lifetimes, threading, error types, and build configuration.

Hand off rather than restating; each row names a public URL in case the concept skill is not installed:

QuestionSkill, if installedOtherwise
DQL syntax, operators, statement semanticswrite-dqlhttps://docs.ditto.live/dql/dql
Which CRDT type, embed vs split, document size, dql_strict_modedata-modelinghttps://docs.ditto.live/key-concepts
What a subscription is, mesh propagationsync-subscriptionshttps://docs.ditto.live/sdk/latest/sync/syncing-data
Transport selection strategy, platform radiosditto-mesh-networkinghttps://docs.ditto.live/sdk/latest/sync/customizing-transport-configurations
v4 → v5 migration procedure—https://docs.ditto.live/sdk/latest/migration-guides/kotlin-v4 and https://docs.ditto.live/sdk/latest/migration-guides/kotlin-dql

Supported versions and packages

Packages: com.ditto:ditto-kotlin (v5; KMP root — Gradle resolves -android / -jvm) and live.ditto:ditto (4.14.x, Android-only). Published API reference: https://software.ditto.live/kotlin/ditto-kotlin/5.2.0/api-reference/.

references/versions.md owns every fact that differs across version lines, including package coordinates per version and the minimum toolchain. v4 and v5 are different APIs, not two versions of one.

For new apps and POCs, check the package manager for the latest stable release and use that resolved version. 5.2.0 is the latest version this skill knows about, not a permanent recommendation or claim about today’s latest release. Preserve an existing project’s selected version unless an upgrade is requested. If a newer release is found, read its release notes/API changes and validate the generated code. If lookup is unavailable, state that limitation; do not silently call 5.2.0 “latest”. Resolve Maven Central metadata and share the selected version across Kotlin and native binaries; see setup.md.

Read sdk-5.2.md when adopting 5.2.0 features or reviewing an upgrade: encryption, store deletion, startup configuration and behavior fixes.

Capability exceptions

Full twenty-row evidence map: README.md, for capability reviews only.

IDCapabilitySupportRequired caveat
CAP-13Presence graph and connection requestspartialGraph, metadata and observe() are docs-verified; connectionRequestHandler only fires with a second peer and was not executed. presence.md
CAP-20Preview and platform-specific APIspreviewData Streams: 5.1.0+, @file:OptIn(com.ditto.kotlin.PreviewDataStreams::class), "breaking changes without notice", NGN required; two-peer fixture executed on the JVM. data-streams.md. Multicast: beta from 5.1.0, off by default. preview.md

Minimum viable app

Open → subscribe → observe → write (JVM shown; on Android hold the instance in Application; no ditto-binaries classifier is needed there):

fun main() = runBlocking {
    val ditto = DittoFactory.create(
        DittoConfig(
            databaseId = "my-app",
            connect = DittoConfig.Connect.SmallPeersOnly(),
            persistenceDirectory = "/tmp/ditto-data",
        )
    )
    ditto.setOfflineOnlyLicenseToken(System.getenv("DITTO_OFFLINE_LICENSE"))
    ditto.sync.start()

    val subscription = ditto.sync.registerSubscription("SELECT * FROM cars")
    val observer = ditto.store.registerObserver("SELECT * FROM cars") { result ->
        println("cars: ${result.items.size}")   // extract inside the lambda!
    }
    ditto.store.execute(
        "INSERT INTO cars DOCUMENTS (:car) ON ID CONFLICT DO UPDATE",
        mapOf("car" to mapOf("_id" to "car1", "make" to "Toyota"))
    )

    observer.close(); subscription.close(); ditto.sync.stop(); ditto.close()
}

Complete program, dependencies and run transcripts (5.2.0/5.1.0/5.0.3 JVM and Android, 4.14.7 Android): examples/minimal-app.md.

Concurrency and threading model

Read concurrency.md for the per-API table and reasoning. Core constraints:

  • Observer handlers start on the SDK's scope: the main thread on Android (MainScope()), Dispatchers.Default on JVM — source-verified and executed on a Pixel. Treat every handler as UI-thread code.
  • Hop inside the handler: withContext(Dispatchers.IO) for extraction and decode, then plain data back to the UI. The result stays open while the handler is suspended. .flowOn() does not reach the observe() transform — it runs on the SDK scope.
  • DittoFactory.create, sync.start()/stop(), registerSubscription, registerObserver are not suspend. store.execute, store.collect, auth.login, attachment fetches are suspend.
  • Handlers are serialized with back-pressure: the next event is produced only after the handler returns, coalescing intermediate changes. Never launch per event, never .buffer(); use .conflate()/stateIn for latest-only UI, collect() for ordered pipelines.
  • Cancelling the collecting scope (viewModelScope, lifecycleScope) closes an observe()/collect() observer; a registerObserver handle needs close().
  • expirationHandler is a property holding a suspend lambda — call login directly in its body, never inside launch { }.

Object lifetimes and retention

One Ditto per process, owned by Application/a DI singleton (Android) or a top-level holder (JVM); closing it closes every child resource. Retain DittoSyncSubscription and registerObserver handles so you can close() them; never retain DittoQueryResult or its items — they die when the handler returns (IllegalStateException: Query result item has already been closed.). Flows from observe()/collect() need no handle: the collecting scope is the lifetime. Do not cache ditto.sync.subscriptions or ditto.store.observers; they are live views. Handle table and per-component cleanup: lifetimes.md.

Anti-patterns

Read anti-patterns.md for wrong/right forms.

  • Returning result.items (or the result) from a handler or observe() transform: Query result item has already been closed. / "You must not return DittoQueryResult".
  • An exception escaping a registerObserver handler: the SDK logs Unhandled exception in observer event handler and closes the observer.
  • .buffer() on an observe() chain: unbounded queue, eventual OOM.
  • scope.launch { … } per event inside a handler: lost ordering, closed-item crashes.
  • .flowOn(Dispatchers.IO) to move decoding off main: no effect on the transform.
  • Casting item.value["x"] as String: ClassCastException — use .stringOrNull or jsonString().
  • setExpirationHandler(…) does not compile; launch around auth.login abandons it.
  • Plain INSERT of an existing _id: Identifier conflict … FAIL conflict policy — add ON ID CONFLICT DO UPDATE.
  • JVM without ditto-binaries: Unable to find binary 'libdittoall…' on create.

Version differences

Concern4.14.x5.0.x / 5.1.x / 5.2.x
Package / namespacelive.ditto:ditto, live.ditto.* (Android-only)com.ditto:ditto-kotlin, com.ditto.kotlin.* (Android + JVM)
InitializationDitto(deps, DittoIdentity.…)DittoFactory.create(DittoConfig(…)), not suspend
Start syncditto.startSync()ditto.sync.start()
Mutating DQLrequires ditto.disableSyncWithV3() first (else StoreError: … disable sync with V3 …, executed)works by default
Query modellegacy collection API (deprecated) + DQLDQL only, via ditto.store.execute
Observersnon-suspend callback, DittoSignalNextsuspend handler / observe() Flow / collect()
ErrorsDittoErrorDittoException sealed hierarchy
On-disk storedefault dir dittodefault dir ditto-{databaseId} for new stores; a default-path v4 store is picked up

Full delta, on-disk migration and removed-API table: references/versions.md.

References

Choose the matching row and section; alternatives, not a reading checklist.

If you need to know…Read
How do I install it and what are the platform minimums?setup.md
How do I open Ditto, authenticate, and start syncing?connect.md
Which thread does a handler run on; how do I hop safely?concurrency.md
What must I retain or close, and where?lifetimes.md
Diagnose a symptomanti-patterns.md, then errors.md
How do I change which transports are used?transports.md
How do I subscribe and check sync status?sync.md
How do I run DQL, transact, index, or evict?store.md
How do observers, Flows, diffing and back-pressure work?observers.md
How do I store and fetch a file?attachments.md
Who else is on the mesh right now?presence.md
How do I turn on logs and see disk usage?observability.md
How do I test code that uses Ditto?testing.md
What changed between 4.14.x and 5.x, on disk and in the API?versions.md
What is preview or platform-specific here?preview.md
Stream raw bytes to one peer (Data Streams, preview)data-streams.md; concepts in the data-streams skill
How do I generate a whole runnable Gradle project?gradle-project-generation.md
Where should the instance live; which observer shape?best-practices.md
Build a complete exampleminimal-app.md (compiled and run)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.