Couchbase Lite for a Kotlin Multiplatform project

I’m evaluating Couchbase Lite for a project I’m building with Kotlin Multiplatform. The idea is to have most of the code in a pure Kotlin module and then build as little platform-specific code as possible to glue everything.

My target platforms are the JVM and Android, so I have an artifact for each: com.couchbase.lite:couchbase-lite-java and com.couchbase.lite:couchbase-lite-android-ktx. So far, so good.

The problem is that there is no common artifact both of them include, so even if 99% of my repository classes are the same in Android and in desktop (only the way to create a Database instance changes a bit, by needing the Android context and there are tiny changes in some factories), I’m forced, unless I’m overlooking something, to duplicate all my methods in both modules. It works, but it feels dirty. :slight_smile:

If I only had an artifact with interfaces for Database, MutableDocument, etc. that both the Android and the JVM library included transitively, I could include that one in my common module, implement there all the functionality and just leave the constructors themselves for the specific platforms.

Is there any other way you can think of achieving this code reuse? Is this something in the dev team backlog?

Hi @serandel

Couchbase Lite does not, at the moment, support Kotlin on the JVM. The Kotlin extension package, couchbase-lite-android-ktx is Android only.

In addition, you are quite correct that the Android platform is substantially different than the JVM platform. Our internal source structure is almost identical to yours: we have a big bunch of common code with thin adaptation layers for each of the 6 specific platform that the “Java” group supports: JVM, Android and Android Kotlin extensions, for each of the two variants, Community and Enterprise.

Here is my suggestion: We try very hard to make sure that the JVM and Android APIs are identical. I would think that you could write all of your code, pretty much agnostic to which of the two libraries it will use, at runtime. The android version of your product would be based (like ours!) on a large base of common code, a top end shim for for the target platform, and the appropriate back-end CBL library, for that platform. The JVM version would be a different shim and a different library.

If our code is any guide, that should work for you and be relatively easy to set up, with gradle.

1 Like

Thanks, Blake, that

Thanks, Blake, that’s exactly my idea. Nevertheless, for the common code I would need something that doesn’t depend on either Android or the Java standard library. Pure Kotlin would be fab, but something like just Java interfaces for Database, MutableDocument, etc. would also work. Platform libraries should include them transitively and implement them with their own tweaks. But I need some Couchbase types to use in my common module.

Just to ensure I’m not misunderstanding you, this is an excerpt of my Gradle build:

sourceSets {
    val commonMain by getting {
        dependencies {
           // What artifact could I put here?
           // I need common superclasses or interfaces of both the Android and the JVM types 
           // to write common code, as queries, converting from domain objects to documents, etc.
        }
    }
    val androidMain by getting {
        dependencies {
            implementation("com.couchbase.lite:couchbase-lite-android-ktx:$couchbaseVersion")
        }
    }
    val jvmMain by getting {
        dependencies {
            implementation("com.couchbase.lite:couchbase-lite-java:$couchbaseVersion")
        }
    }
}

Is there something I’m overlooking? I appreciate your help. :slight_smile:

By the way, I copied parts of the ktx library to my JVM target (like QueryToFlow) and everything worked without a hitch. I suspect that most of the extensions could be migrated to JVM without having to write any additional code. As a fan of Kotlin for backend and desktop, I really encourage the team to do so, officially, before I publish that to my Github. :slight_smile:

@serandel

I just love this idea but I don’t see any simple way of doing what you describe. The design of the product code that I’ve inherited is almost exactly what you describe, here: a “common” codebase that is pure Java on which two platform specific code-bases depend. The result is that the API you see is code that is completely platform specific. You might have a look at repo: couchbase-lite-java-ce-root to see what I mean.

You should get in touch with @jeff.lockhart who is doing something similar.

@serandel I’ve been working on a Couchbase Lite Kotlin Multiplatform library and have the full CBL API working for Android, JVM, iOS, and macOS, with experimental native support for Linux and Windows via the CBL C SDK. I’m hoping to release the library soon. It’s only blocked on this Okio PR in order to avoid releasing with an Okio fork.

Publishing a Kotlin Multiplatform library with only the CBL Android and Java SDKs would be interesting for this use case of sharing code specifically between those two platforms. It’d be easier than unifying the API with native (which is what required introducing Okio’s Source type to replace the JVM-specific InputStream APIs). Likely the only thing blocking @blake.meike from publishing the Android and Java SDKs as a KMP library with those two targets is the fact that the libraries are mostly written in Java. I’m not sure if the Kotlin Multiplatform plugin would support Java sources. It might not even need Kotlin’s expect/actual mechanism. Just the shared Java sources in the common source set, with the few platform-specific APIs being in the platform source sets. If this were possible, it’d just be a matter of reworking the Gradle configuration and source set definitions in order to publish the libraries in this way, along with a Kotlin common API. This would actually solve my Gradle Module Metadata publication need too, as KMP automatically publishes this metadata.

I looked into this some more to refresh my knowledge of using Java source files in Kotlin Multiplatform. You can do this with a jvm target, but not when you have an android target.

This build.gradle.kts works to compile Java sources in a jvm target:

plugins {
    kotlin("multiplatform")
}

kotlin {
    jvm {
        withJava()
    }
}

But this fails after adding the android target:

plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

kotlin {
    jvm {
        withJava()
    }
    android()
}

The ‘java’ plugin has been applied, but it is not compatible with the Android plugins.

It could be possible to compile all the common Java sources in a separate Java module, then consume that build as an API dependency in the KMP common source set. Then you’d just need to convert the few APIs that are Java or Android specific to Kotlin, or consume them from their own Java and Android specific modules, to publish as a KMP library.