Suraj Sharma
Suraj Sharma

Reputation: 3

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"
]

Upvotes: 0

Views: 1578

Answers (2)

Samuel Marks
Samuel Marks

Reputation: 1884

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.

Upvotes: 0

Caden Raquel
Caden Raquel

Reputation: 1

I've been running into the same issue on my own KMP project. I think the problem is that ktor-client-content-negotiation doesn't support WASM, but it does support running on Android natively and on desktop with the JVM. The error describes that it is searching for variants of the library that support your targeted platform, hence why it says it needs to be usable by the Kotlin Runtime as well as on WASM.

... the consumer needed a component for use during 'kotlin-runtime', as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm'

For your desktop build org.jetbrains.kotlin.platform.type would be jvm, which exists as a variant of the library, but one with wasm doesn't.

Unfortunately that means that you can't use this dependency for any build that targets the web, and you'll need to find an alternative, unless I'm mistaken.

Upvotes: 0

Related Questions