Search code examples
androidkotlinkotlin-multiplatform

Kotlin Multiplatform Android Imports won't resolve


I have created a very simple KMP project, with the following structure:

-Root
  --app
  --gradle
--SharedCode
  --src\commonMain\kotlin\actual.kt
  --src\iosMain\kotlin\actual.kt
  --scr\androidMain\kotlin\actual.kt
  --build.gradle.kts
--native
  --KotlinIOS
    --iOS project (xcodeproj, etc)

Everything works, and the basic project work on both Android and iOS platforms.

But when I try to use an android-specific import statement in my androidMain directory, the import statement won't resolve:

import android.os.build // Android Studio can't find this

actual fun platformName(): String {
    return "Android"
}

It is weird, since the iOS package is using iOS-specific imports successfully:

import platform.UIKit.UIDevice // This import works

actual fun platformName(): String {
    return UIDevice.currentDevice.systemName() +
            " " + UIDevice.currentDevice.systemVersion
}

Any suggestions about what I may need to configure to get my Android import to work?

This is my build.gradle.kts file for completeness:

import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

plugins {
    kotlin("multiplatform")
}

kotlin {
    //select iOS target platform depending on the Xcode environment variables
    val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
            ::iosArm64
        else
            ::iosX64

    iOSTarget("ios") {
        binaries {
            framework {
                baseName = "SharedCode"
            }
        }
    }


    jvm("android")

    sourceSets["commonMain"].dependencies {
        implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
    }

    sourceSets["androidMain"].dependencies {
        implementation("org.jetbrains.kotlin:kotlin-stdlib")
    }
}

val packForXcode by tasks.creating(Sync::class) {
    val targetDir = File(buildDir, "xcode-frameworks")

    /// selecting the right configuration for the iOS
    /// framework depending on the environment
    /// variables set by Xcode build
    val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
    val framework = kotlin.targets
        .getByName<KotlinNativeTarget>("ios")
        .binaries.getFramework(mode)
    inputs.property("mode", mode)
    dependsOn(framework.linkTask)

    from({ framework.outputDirectory })
    into(targetDir)

    /// generate a helpful ./gradlew wrapper with embedded Java path
    doLast {
        val gradlew = File(targetDir, "gradlew")
        gradlew.writeText("#!/bin/bash\n"
                + "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
                + "cd '${rootProject.rootDir}'\n"
                + "./gradlew \$@\n")
        gradlew.setExecutable(true)
    }
}

tasks.getByName("build").dependsOn(packForXcode) 

Solution

  • You do not have the android required definitions in your gradle build script. Here's a basic gradle script that works for me:

    import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
    import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
    
    buildscript {
        repositories {
            mavenLocal()
            maven("https://kotlin.bintray.com/kotlinx")
            maven("https://dl.bintray.com/jetbrains/kotlin-native-dependencies")
            maven("https://dl.bintray.com/kotlin/kotlin-eap")
            maven("https://plugins.gradle.org/m2/")
            google()
            jcenter()
        }
    
        dependencies {
            classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50")
            classpath("com.android.tools.build:gradle:3.5.0")
            classpath("co.touchlab:kotlinxcodesync:0.1.5")
        }
    }
    
    plugins {
        id ("com.android.library") version "1.3.50"
        id ("org.jetbrains.kotlin.multiplatform") version "1.3.50"
    }
    
    allprojects {
        repositories {
            mavenLocal()
            google()
            jcenter()
            mavenCentral()
            maven("https://dl.bintray.com/kotlin/kotlin-eap")
            maven("https://kotlin.bintray.com/ktor")
            maven("https://kotlin.bintray.com/kotlinx")
        }
    }
    
    group = "com.example.mykmp"
    version = "0.0.1"
    
    apply(plugin = "maven-publish")
    apply(plugin = "com.android.library")
    apply(plugin = "kotlin-android-extensions")
    
    android {
        compileSdkVersion(29)
        defaultConfig {
            minSdkVersion(21)
            targetSdkVersion(29)
        }
    }
    
    kotlin {
        android()
        val frameworkName = "mykmp"
        val ios = iosX64("ios") {
            binaries.framework {
                baseName = frameworkName
            }
        }
        sourceSets {
            commonMain {
                dependencies {
                    implementation(kotlin("stdlib-common"))
                }
            }
            commonTest {
                dependencies {
                    implementation(kotlin("test-common"))
                    implementation(kotlin("test-annotations-common"))
                }
            }
            val androidMain by getting {
                dependencies {
                    implementation(kotlin("stdlib-jdk8"))
                }
            }
            val androidTest by getting {
                dependencies {
                    implementation(kotlin("test"))
                    implementation(kotlin("test-junit"))
                }
            }
            val iosMain by getting {
                dependencies {
                    implementation(kotlin("stdlib"))
                }
            }
            val iosTest by getting {
                dependencies {
                    implementation(kotlin("stdlib"))
                }
            }
        }
    
        tasks.create("framework", FatFrameworkTask::class) {
            baseName = frameworkName
            from(
                ios.binaries.getFramework("DEBUG")
            )
            destinationDir = buildDir.resolve("xcode-frameworks")
            group = "iOS frameworks"
            description = "Builds a simulator only framework to build from xcode directly"
            doLast {
                val file = File(destinationDir, "gradlew")
                file.writeText(text = "#!/bin/bash\nexport JAVA_HOME=${System.getProperty("java.home")}\ncd ${rootProject.rootDir}\n./gradlew \$@\n")
                file.setExecutable(true)
            }
        }
    }
    
    tasks.getByName("build").dependsOn(packForXcode)
    

    Also, in the settings.gradle.kts, I have this:

    pluginManagement {
        resolutionStrategy {
            eachPlugin {
                if (requested.id.id == "kotlin-multiplatform") {
                    useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
                }
                if (requested.id.id == "com.android.library") {
                    useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
                }
                if (requested.id.id == "kotlinx-serialization") {
                    useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
                }
            }
        }
    }
    

    Otherwise, it might complain that com.android.library could not be found.

    I would also recommend Invalidate Cache/Restart on Android Studio or IntelliJ after that.

    Hope that helps.