Marat
Marat

Reputation: 6703

Android Studio: "shared" module directories of KMM project keep getting unmarked as sources root

I created a new project in Android Studio using KMM wizard. I was following handson tutorial and I noticed that I don't have an option of creating a package inside of some directories. Specifically, inside of "shared" module only kotlin directory of androidMain folder is always marked as "sources root".

I manually marked kotlin directories in other folders (commonMain, iosMain) as "sources root". I also marked sqldelight directory inside of commonMain as "sources root".

But Android Studio keeps reverting that state back periodically. I don't know what is causing this issue. It also shows that kotlin directory of androidMain folder is set as "sources root" and also is not. Which is weird, directory can't be set and unset as "sources root" at the same time.

Is it a bug of Android Studio, KMM Plugin or some kind of setting in preferences?

Android Studio version: 4.1.1

KMM plugin version: 0.1.3-release-54-Studio4.1

enter image description here

enter image description here

EDIT:

build.gradle.kts for shared module:

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

plugins {
    kotlin("multiplatform")
    kotlin("plugin.serialization")
    id("com.android.library")
    id("kotlin-android-extensions")
    id("com.squareup.sqldelight")
}
group = "com.example.kmmapplication"
version = "1.0-SNAPSHOT"

repositories {
    gradlePluginPortal()
    google()
    jcenter()
    mavenCentral()
}
kotlin {
    android()
    ios {
        binaries {
            framework {
                baseName = "shared"
            }
        }
    }

    // Block from https://github.com/cashapp/sqldelight/issues/2044#issuecomment-721299517.
    val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
    if (onPhone) {
        iosArm64("ios")
    } else {
        iosX64("ios")
    }

    val coroutinesVersion = "1.3.9-native-mt"
    val serializationVersion = "1.0.1"
    val ktorVersion = "1.4.2"
    val sqlDelightVersion: String by project

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
                implementation("io.ktor:ktor-client-core:$ktorVersion")
                implementation("io.ktor:ktor-client-serialization:$ktorVersion")
                implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-android:$ktorVersion")
                implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion")
            }
        }
        val androidTest by getting {
            dependencies {
                implementation(kotlin("test-junit"))
                implementation("junit:junit:4.13.1")
            }
        }
        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-ios:$ktorVersion")
                implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
            }
        }
        val iosTest by getting
    }
}
android {
    compileSdkVersion(29)
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdkVersion(24)
        targetSdkVersion(29)
        versionCode = 1
        versionName = "1.0"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
}

sqldelight {
    database("AppDatabase") {
        packageName = "com.example.kmmapplication.shared.cache"
    }
}

val packForXcode by tasks.creating(Sync::class) {
    group = "build"
    val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
    val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
    val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
    val framework =
        kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
    inputs.property("mode", mode)
    dependsOn(framework.linkTask)
    val targetDir = File(buildDir, "xcode-frameworks")
    from({ framework.outputDirectory })
    into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)

settings.gradle.kts:

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        jcenter()
        mavenCentral()
    }
    resolutionStrategy {
        eachPlugin {
            if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
                useModule("com.android.tools.build:gradle:4.0.1")
            }
        }
    }
}
rootProject.name = "KMMApplication"


include(":androidApp")
include(":shared")

gradle-wrapper.properties:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

Upvotes: 5

Views: 3522

Answers (1)

Kevin Galligan
Kevin Galligan

Reputation: 17302

These ios target definitions conflict:

    ios { //Target 1
        binaries {
            framework {
                baseName = "shared"
            }
        }
    }

    // Block from https://github.com/cashapp/sqldelight/issues/2044#issuecomment-721299517.
    val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
    if (onPhone) {
        iosArm64("ios") //Target 2
    } else {
        iosX64("ios") //Target 3
    }

You have ios, which is a combined target for arm and x64, then the individual targets iosArm64 and iosX64`. I don't know if that's what's causing the IDE issue, but it's certainly confusing.

The 2nd two don't define frameworks as they were taken from a context that would use cocoapods. If you look at the sqldelight issue comment, it came from me, and ultimately came from KaMPKit: https://github.com/touchlab/KaMPKit/blob/master/shared/build.gradle.kts#L28.

To get the IDE working, I'd suggest removing the first ios target. However, again, the sample from the sqldelight issue assumes you have cocoapods configured. You'll either need to add cocoapods or update both the target config and packForXcode.

The targets would look something like the following.

val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
    if (onPhone) {
        iosArm64("ios") {
          binaries {
              framework {
                  baseName = "shared"
              }
          }
      }
    } else {
        iosX64("ios") {
          binaries {
             framework {
                 baseName = "shared"
             }
          }
       }
    }

As an alternative, I'd suggest just using KaMPKit as a base for your project until you're more familiar with the KMM plugin samples and config options. They don't quite work out of the box yet.

Upvotes: 3

Related Questions