I am working on a compose multiplatform project an...
# multiplatform
t
I am working on a compose multiplatform project and I am taking use of ViewModels there. My desktop app keeps crashing whenever I try to use
viewModelScope
. I did follow https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-viewmodel.html#using-viewmodel-in-common-code and have the
kotlinx-coroutines-swing
dependency for the desktopMain sourceSet yet it crashes with the following log:
Copy code
Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: Exception while trying to handle coroutine exception
	at kotlinx.coroutines.CoroutineExceptionHandlerKt.handlerException(CoroutineExceptionHandler.kt:36)
	at kotlinx.coroutines.internal.CoroutineExceptionHandlerImpl_commonKt.handleUncaughtCoroutineException(CoroutineExceptionHandlerImpl.common.kt:38)
...
    at com.tanishranjan.byteblaster.features.splash.presentation.SplashViewModel.<init>(SplashViewModel.kt:21)
	at com.tanishranjan.byteblaster.features.splash.di.SplashModuleKt$splashModule$lambda$0$$inlined$viewModelOf$default$1.invoke(ViewModelOf.kt:227)
	at com.tanishranjan.byteblaster.features.splash.di.SplashModuleKt$splashModule$lambda$0$$inlined$viewModelOf$default$1.invoke(ViewModelOf.kt:51)
...
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
...
    Suppressed: java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used
		at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:111)
		at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:92)
		at kotlinx.coroutines.internal.DispatchedContinuationKt.safeIsDispatchNeeded(DispatchedContinuation.kt:262)
		at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:315)
		at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
		... 223 more
	Caused by: java.lang.NoClassDefFoundError: android/os/Looper
        at kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:51)
		at kotlinx.coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:53)
		at kotlinx.coroutines.internal.MainDispatcherLoader.loadMainDispatcher(MainDispatchers.kt:34)
		at kotlinx.coroutines.internal.MainDispatcherLoader.<clinit>(MainDispatchers.kt:18)
		at kotlinx.coroutines.Dispatchers.getMain(Dispatchers.kt:20)
		at androidx.lifecycle.MainDispatcherChecker.updateMainDispatcherThread(MainDispatcherChecker.desktop.kt:27)
		at androidx.lifecycle.MainDispatcherChecker.isMainDispatcherThread(MainDispatcherChecker.desktop.kt:55)
		at androidx.lifecycle.LifecycleRegistry_desktopKt.isMainThread(LifecycleRegistry.desktop.kt:19)
		at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.jvm.kt:297)
		at androidx.lifecycle.LifecycleRegistry.setCurrentState(LifecycleRegistry.jvm.kt:101)
		at androidx.compose.ui.scene.ComposeContainer.updateLifecycleState(ComposeContainer.desktop.kt:463)
		at androidx.compose.ui.scene.ComposeContainer.onWindowFocusChanged(ComposeContainer.desktop.kt:225)
		at androidx.compose.ui.scene.ComposeContainer.setWindow(ComposeContainer.desktop.kt:317)
		at androidx.compose.ui.scene.ComposeContainer.<init>(ComposeContainer.desktop.kt:185)
		at androidx.compose.ui.scene.ComposeContainer.<init>(ComposeContainer.desktop.kt:84)
		at androidx.compose.ui.awt.ComposeWindowPanel.<init>(ComposeWindowPanel.desktop.kt:52)
		at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:66)
		at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:64)
Can someone please help me with this?
🧵 2
w
I hit this recently. It's caused by importing a -ktx dependency, which pulls in kotlinx-coroutines-android into the desktop JVM build. Strip all the -ktx references from your commonMain dependencies in favor of the non-ktx ones. Or it could be some other library. Dumping the desktop dependencies will narrow it down.
t
I don't think that is the cause here though. build.gradle.kts:
Copy code
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.composeMultiplatform)
    alias(libs.plugins.composeCompiler)
    alias(libs.plugins.kotlinxSerialization)
    alias(libs.plugins.sqlDelight)
}

sqldelight {
    databases {
        create("ByteBlasterDB") {
            packageName.set("com.tanishranjan.byteblaster.database")
        }
    }
}

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

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

    jvm("desktop")

    sourceSets {
        val desktopMain by getting

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

            implementation(libs.android.driver)
            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.androidx.lifecycle.viewmodel)
            implementation(libs.androidx.lifecycle.viewmodel.compose)
            implementation(libs.androidx.lifecycle.runtime.compose)

            implementation(libs.navigation.compose)

            implementation(libs.ktor.client.core)
            implementation(libs.ktor.client.websockets)
            implementation(libs.ktor.client.serialization)

            implementation(libs.sqldelight.runtime)

            implementation(libs.koin.core)
            implementation(libs.koin.compose)
            implementation(libs.koin.compose.viewmodel)

            implementation (libs.androidx.camera.core)
            implementation (libs.androidx.camera.camera2)
            implementation (libs.androidx.camera.view)
            implementation (libs.androidx.camera.lifecycle)
        }
        nativeMain.dependencies {
            implementation(libs.native.driver)
        }
        iosMain.dependencies {
            implementation(libs.ktor.client.darwin)
        }
        desktopMain.dependencies {
            implementation(compose.desktop.currentOs)
            implementation(libs.ktor.client.cio)
            implementation(libs.sqldelight.driver)
            implementation(libs.kotlinx.coroutines.swing)
        }
    }
}

android {
    namespace = "com.tanishranjan.byteblaster"
    compileSdk = libs.versions.android.compileSdk.get().toInt()

    defaultConfig {
        applicationId = "com.tanishranjan.byteblaster"
        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
    }
}

dependencies {
    debugImplementation(compose.uiTooling)
}

compose.desktop {
    application {
        mainClass = "com.tanishranjan.byteblaster.MainKt"

        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "com.tanishranjan.byteblaster"
            packageVersion = "1.0.0"
        }
    }
}
libs.version.toml:
Copy code
[versions]
agp = "8.6.1"
android-compileSdk = "35"
android-minSdk = "24"
android-targetSdk = "35"
androidx-activityCompose = "1.9.3"
androidx-appcompat = "1.7.0"
androidx-constraintlayout = "2.2.0"
androidx-core-ktx = "1.15.0"
androidx-espresso-core = "3.6.1"
androidx-lifecycle = "2.8.4"
androidx-material = "1.12.0"
androidx-test-junit = "1.2.1"
cameraCore = "1.4.1"
cameraCamera2 = "1.4.1"
compose-multiplatform = "1.7.0"
junit = "4.13.2"
kotlin = "2.0.21"
kotlinx-coroutines = "1.10.1"
lifecycleViewmodelCompose = "2.8.4"
navigationCompose = "2.8.0-alpha11"
ktor = "3.0.2"
sql-delight = "2.0.2"
koin = "4.0.0"


[libraries]
android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sql-delight" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
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" }
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sql-delight" }
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
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-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktor" }
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sql-delight" }
sqldelight-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sql-delight" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }


[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sql-delight" }
w
You have androidx.camera under commonMain though.
Although it's easiest to just dump the dependencies for the desktop runtime config and search for kotlinx-coroutines-android.
t
Yea that was a mistake. Thank Winson! Got everything working now :)
🙌 1