rickclephas / KMP-NativeCoroutines

Library to use Kotlin Coroutines from Swift code in KMP apps
MIT License
1.07k stars 32 forks source link

Cannot find Methods and Properties annotated with @NativeCoroutines in iOS. #112

Closed hrodrick closed 1 year ago

hrodrick commented 1 year ago

We are having an issue since version 1.0 (tried with 1.0.7, 1.0.8 and 1.0.9) where the methods and attributes annotated with @NativeCoroutines can not be found on the iOS project.

To verify and test this you can clone the repo from this link https://github.com/rootstrap/FlowForms/tree/feature/binding-fields (branch is feature/binding-fields). Sync with gradle, compile in android, then compile in Xcode.

This is an example case. It happens with the rest of @NativeCoroutines methods and fields, like validateOnValueChange.

image

Here we are using statusNative, based on the migration guide it should be used as just status, but it isn't recognized neither. FFCFlowForm stands for the FlowForm class in a submodule of a KMP library (FlowForms-Core, which is a module within the repository, imported by the shared module).

Notes :

  1. The generated FlowFormsNative file is generated like this, where the specific library imports figure as red, but we don't know why or if it really matters. Note that the status field is created as statusNative instead of status šŸ¤”

    image
  2. This didn't happened before 1.0.0 and the library was working as expected. (but we can't revert to before 1.0 because of a gradle update on our side)

rickclephas commented 1 year ago

Your shared module has an implementation dependency on the FlowForms-Core module. Please try and make this an api implementation.

The generated FlowFormsNative file is generated like this, where the specific library imports figure as red, but we don't know why or if it really matters.

Not a 100% sure, but that might be related to it being a generated file. As long as the project compiles this shouldn't be an issue.

Note that the status field is created as statusNative instead of status šŸ¤”

That is correct. On the Kotlin side you'll have status and statusNative. The @NativeCoroutines annotation on status makes sure that it's hidden from ObjC, which in turn allows the @ObjCName annotation on statusNative to instruct the compiler to use status as its ObjC/Swift name.

This didn't happened before 1.0.0 and the library was working as expected.

The major difference between 0.x and 1.0 is that 1.0 is using KSP to generate the Native extension properties/functions. In 0.x the plugin would modify your existing classes which meant the Native properties/functions were actually part of your classes.

hrodrick commented 1 year ago

Hi! Thanks for the explanation. I tried using api instead of implementation but the result is the same, i've also tried cleaning the cache and built files on both Android Studio and Xcode. Still can not found the methods and props annotated with @NativeCoroutine šŸ˜ž

rickclephas commented 1 year ago

Sorry forgot to mention that once it's an api dependency you will need to tell Kotlin to export the dependency to ObjC. https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#export-dependencies-to-binaries

jnelle commented 1 year ago

@rickclephas could you provide an example? I can't reproduce this for my project. My build.gradle.kts is the following:

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("com.android.library")
    id("com.rickclephas.kmp.nativecoroutines")
}

kotlin {
    android {
        compilations.all {
            kotlinOptions {
                jvmTarget = "1.8"
            }
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
            isStatic = true
        }

        extraSpecAttributes["resources"] =
            "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
    }

    kotlin.sourceSets.all {
        languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
                implementation("io.ktor:ktor-client-core:2.3.0")
                implementation("io.ktor:ktor-client-content-negotiation:2.3.0")
                implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.0")
                implementation("io.ktor:ktor-client-cio:2.3.0")
                implementation("dev.gitlive:firebase-auth:1.8.1")
                implementation("dev.gitlive:firebase-firestore:1.8.1")
                api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-8")
                implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
                implementation("io.github.aakira:napier:2.6.1")

                api("io.insert-koin:koin-core:3.4.0")
                implementation(project(":domain"))
                implementation(project(":courses"))
                implementation(project(":auth"))

            }
        }
rickclephas commented 1 year ago

@jnelle I see you are using CocoaPods. In that case add the exports to the kotlin.cocoapods.framework section:

export(project(":dependency"))

Also make sure to use api instead of implementation for your project dependencies that are using KMP-NativeCoroutines.

P.S. you'll also need to apply the KSP plugin.

jnelle commented 1 year ago

Thanks for the quick reply!

I changed my build.gradle.kts in /shared to the following:


plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("com.android.library")
    id("com.rickclephas.kmp.nativecoroutines")
    id("com.google.devtools.ksp") <-- was added
}

kotlin {
    android {
        compilations.all {
            kotlinOptions {
                jvmTarget = "1.8"
            }
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
            isStatic = true
            export("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-8") <-- was added
        }

        extraSpecAttributes["resources"] =
            "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
    }

    kotlin.sourceSets.all {
        languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
                implementation("io.ktor:ktor-client-core:2.3.0")
                implementation("io.ktor:ktor-client-content-negotiation:2.3.0")
                implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.0")
                implementation("io.ktor:ktor-client-cio:2.3.0")
                implementation("dev.gitlive:firebase-auth:1.8.1")
                implementation("dev.gitlive:firebase-firestore:1.8.1")
                implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
                implementation("io.github.aakira:napier:2.6.1")

                implementation(project(":domain"))
                implementation(project(":courses"))
                implementation(project(":auth"))

                api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-8")
                api("io.insert-koin:koin-core:3.4.0")

            }
        }

But unfortunately I got the following error:

image

with the following stacktrace:

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_SharedKmm_viewmodel_coreKMMViewModel", referenced from:
      objc-class-ref in KMMViewModel.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

P.S: I'm only using KMP-NativeCoroutines in my shared module

rickclephas commented 1 year ago

@jnelle alright in that case just applying the KSP plugin should be enough. You shouldn't need to export KMM-ViewModel. I guess exporting it changes the name of Kmm_viewmodel_coreKMMViewModel.

jnelle commented 1 year ago

Thank you very much, this worked for me! :)

hrodrick commented 1 year ago

Hi @rickclephas, sorry for the delay to answer. Thank you very much! I was missing that setup. Now it is working for me :)