kotlinkotlin-multiplatformktorktor-client

Ktor Dependency Issue in Kotlin Multiplatform Project for Browser Target


I'm working on a Kotlin Multiplatform (KMP) project to develop a native application for Android, iOS, desktop, and browser platforms. Everything works fine for Android and desktop. However, when trying to build for the browser using WebAssembly (Wasm), I encounter dependency resolution issues with Ktor.

Problem Description When I attempt to build the project for the browser using the command wasmJsBrowserRun -t --quiet, it fails with the following error:

text
Copy code
Could not determine the dependencies of task ':kotlinNpmInstall'.
> Could not resolve all dependencies for configuration ':composeApp:wasmJsNpmAggregated'.
   > Could not resolve io.ktor:ktor-client-content-negotiation:2.3.9.
     Required by:
         project :composeApp
      > No matching variant of io.ktor:ktor-client-content-negotiation:2.3.9 was found. The consumer was configured to find a library for use during 'kotlin-runtime', preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.js.public.package.json' with value 'public-package-json', attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm', attribute 'org.jetbrains.kotlin.wasm.target' with value 'js' but:
          - Variant 'iosArm64ApiElements-published' declares a library:
              - Incompatible because this component declares a component for use during 'kotlin-api', as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' and the consumer needed a component for use during 'kotlin-runtime', as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm'
          - Variant 'iosArm64MetadataElements-published' declares a library:
              - Incompatible because this component declares a component for use during 'kotlin-metadata', as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' and the consumer needed a component for use during 'kotlin-runtime', as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm'
          - Variant 'iosArm64SourcesElements-published' declares a component for use during 'kotlin-runtime':
              - Incompatible because this component declares documentation, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' and the consumer needed a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm'

Project Configuration Here is my build.gradle.kts file:

kotlin
Copy code
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsCompose)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.kotlinSerialization)
}

kotlin {
    @OptIn(ExperimentalWasmDsl::class)
    wasmJs {
        moduleName = "composeApp"
        browser {
            commonWebpackConfig {
                outputFileName = "composeApp.js"
                devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply {
                    static = (static ?: mutableListOf()).apply {
                        // Serve sources to debug inside browser
                        add(project.projectDir.path)
                    }
                }
            }
        }
        binaries.executable()
    }

    androidTarget {
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }

    jvm("desktop")

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true
        }
    }

    sourceSets {
        val desktopMain by getting

        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
            implementation(libs.koin.androidx.compose)
            implementation(libs.koin.android)
            implementation(libs.ktor.client.okhttp)
            implementation(libs.koin.compose)
            implementation(libs.koin.android)
        }

        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)
            implementation(libs.kermit)
            implementation(libs.bundles.ktor)
            implementation(libs.kotlinx.coroutines.core)
            implementation(libs.kotlinx.serialization.json)
            implementation(libs.decompose)
            implementation(libs.decompose.jetbrains)
            implementation(libs.coil3.coil.compose)
            implementation(libs.coil3.network.ktor)
            implementation(libs.koin.core)
        }

        desktopMain.dependencies {
            implementation(compose.desktop.currentOs)
            implementation(libs.ktor.client.cio)
            implementation(libs.kotlinx.coroutine.swing)
        }

        val wasmJsMain by getting {
            dependencies {
                implementation(libs.kermit)
                implementation(libs.bundles.ktor)
                implementation(libs.kotlinx.coroutines.core)
                implementation(libs.kotlinx.serialization.json)
                implementation(libs.decompose)
                implementation(libs.decompose.jetbrains)
                implementation(libs.coil3.coil.compose)
                implementation(libs.coil3.network.ktor)
                implementation(libs.koin.core)
            }
        }

        iosMain.dependencies {
            implementation(libs.ktor.client.darwin)
        }
    }
}

android {
    namespace = "com.winiwayuser"
    compileSdk = libs.versions.android.compileSdk.get().toInt()
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    sourceSets["main"].res.srcDirs("src/androidMain/res")
    sourceSets["main"].resources.srcDirs("src/commonMain/resources")

    defaultConfig {
        applicationId = "com.winiwayuser"
        minSdk = libs.versions.android.minSdk.get().toInt()
        targetSdk = libs.versions.android.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    buildFeatures {
        compose = true
    }
    lint {
        baseline = file("lint-baseline.xml")
    }

    dependencies {
        debugImplementation(compose.uiTooling)
    }
}

dependencies {
    implementation(libs.androidx.material3.android)
    implementation(libs.androidx.startup.runtime)
}

compose.desktop {
    application {
        mainClass = "MainKt"

        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "com.winiwayuser"
            packageVersion = "1.0.0"
        }
    }
}

Here is my libs.version.toml:

[versions]
agp = "8.2.2"
android-compileSdk = "34"
android-minSdk = "24"
android-targetSdk = "34"
androidx-activityCompose = "1.9.0"
androidx-appcompat = "1.7.0"
androidx-constraintlayout = "2.1.4"
androidx-core-ktx = "1.13.1"
androidx-espresso-core = "3.5.1"
androidx-material = "1.12.0"
androidx-test-junit = "1.1.5"
compose-plugin = "1.6.11"
junit = "4.13.2"
kotlin = "2.0.0"
#Added Version Start Here ------------------------
coilComposeVersion = "3.0.0-alpha06"
kermit = "2.0.3"
ktor = "2.3.9"
coroutines = "1.8.1"
decompose = "2.2.2"
decompose-extensionsComposeJetbrains = "2.1.4-compose-experimental"
kotlinxSerializationJson = "1.6.3"
koin = "3.5.3"
koin-Compose = "1.1.2"
logBack = "1.2.11"
material3Android = "1.2.1"
startupRuntime = "1.1.1"


[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
#Added Library Start Here-----------X----------------X---------------------X---------------

#Coil Image loading library
coil3-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coilComposeVersion" }
coil3-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilComposeVersion" }
coil3-coil-compose-andrdoid = { module = "io.ktor:ktor-client-android", version.ref = "coilComposeVersion" }

#Loging Log
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }

#Ktor networking library
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
log-back = { module = "ch.qos.logback:logback-classic", version.ref = "logBack" }

#Coroutines
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutine-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }

#Decopose for navigation and viewmodel
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-jetbrains = { module = "com.arkivanov.decompose:extensions-compose-jetbrains", version.ref = "decompose-extensionsComposeJetbrains" }

#noinspection SimilarGradleDependency
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

#Kotlin Koin
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-Compose" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }

#Android Startup for context and all
androidx-startup-runtime = { group = "androidx.startup", name = "startup-runtime", version.ref = "startupRuntime" }


[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
#Added Plugin Start Here
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

[bundles]
ktor = [
    "ktor-client-content-negotiation",
    "ktor-client-core",
    "ktor-serialization-kotlinx-json",
    "log-back",
    "ktor-logging"
]

Solution

  • As of 15th of July 2024 there is now official ktor support for WASM.

    Three days ago [9th of October 2024] in its 3.0.0 release, it became stable.