rickclephas / KMP-NativeCoroutines

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

Properties and methods annotated with @NativeCoroutines not found in Swift code. #127

Closed AlexIach closed 1 year ago

AlexIach commented 1 year ago

I've just started my KMP journey and I thought that it would be a very good idea to add native coroutines into my test project using this cool library. I was following the instructions to integrate this library and unfortunately I faced exact the same problem that was mentioned in this Closed Issue (Sorry for opening another issue). I've updated my Gradle configuration according to mentioned suggestions but I'm still facing the issue.

Link to test project.

:shared:build.gradle.kts

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    kotlin("plugin.serialization") version "1.5.10"
    id("com.android.library")
    id("com.squareup.sqldelight")
    id("com.rickclephas.kmp.nativecoroutines") version "1.0.0-ALPHA-10"
    id("com.google.devtools.ksp") version "1.8.21-1.0.11"
}

@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
    targetHierarchy.default()

    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/**']"
    }

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

        val ktorVersion = "2.3.3"
        val sqlDelightVersion = "1.5.5"
        val koinVersion = "3.4.3"
        val kotlinxSerializationVersion = "1.5.1"
        val kotlinCoroutinesVersion = "1.7.1"

        val commonMain by getting {
            dependencies {
                // Koin DI
                api("io.insert-koin:koin-core:$koinVersion")
                // Coroutines native
                api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-10")
                // Ktor
                implementation("io.ktor:ktor-client-core:$ktorVersion")
                implementation("io.ktor:ktor-client-json:$ktorVersion")
                implementation("io.ktor:ktor-client-logging:$ktorVersion")
                implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
                // Serialization
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion")
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
                // Sql Delight
                implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion")
                implementation("com.squareup.sqldelight:coroutines-extensions:$sqlDelightVersion")
            }
        }

        val androidMain by getting {
            dependencies {
                implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion")
                implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
                api("io.insert-koin:koin-android:$koinVersion")
            }
        }
        val iosMain by getting {
            dependencies {
                implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
                implementation("io.ktor:ktor-client-darwin:$ktorVersion")
            }
        }
    }
}

sqldelight {
    database("DogifyDatabase") {
        packageName = "com.alex.iachimov.mykmmapplication.db"
    }
}

android {
    namespace = "com.alex.iachimov.mykmapplication"
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    compileSdk = 34
    defaultConfig {
        minSdk = 24
    }
}

Top level build.gradle.kts

plugins {
    //trick: for the same plugin versions in all sub-modules
    id("com.android.application").version("8.1.0").apply(false)
    id("com.android.library").version("8.1.0").apply(false)
    kotlin("android").version("1.8.21").apply(false)
    kotlin("multiplatform").version("1.8.21").apply(false)
}

tasks.register("clean", Delete::class) {
    delete(rootProject.buildDir)
}

buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
        google()
    }

    dependencies {
        classpath("com.squareup.sqldelight:gradle-plugin:1.5.5")
    }
}

allprojects {
    repositories {
        mavenCentral()
        google()
    }
}

Repository class I want to share:

class BreedsRepository : KoinComponent {
    private val remoteSource: BreedsRemoteSource by inject()
    private val localSource: BreedsLocalSource by inject()

    @NativeCoroutines
    val breeds = localSource.breeds

    @NativeCoroutines
    internal suspend fun get() = with(localSource.selectAll()) {
        if (isNullOrEmpty()) {
            return@with fetch()
        } else {
            this
        }
    }

    @NativeCoroutines
    internal suspend fun fetch() = supervisorScope {
        remoteSource.getBreeds().map {
            async { Breed(name = it, imageUrl = remoteSource.getBreedImage(it)) }
        }.awaitAll().also {
            localSource.clear()
            it.map { async { localSource.insert(it) } }.awaitAll()
        }
    }

    @NativeCoroutines
    suspend fun update(breed: Breed) = localSource.update(breed)
}

I can see that generated code is present:

@ObjCName(name = "breeds")
public val BreedsRepository.breedsNative: NativeFlow<List<Breed>>
 get() = breeds.asNativeFlow(null)

But when I'm trying to access generated code in XCode I'm getting errors.

Screenshot 2023-08-10 at 09 41 09

Here is my Pod file:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'iosApp' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for iosApp
  pod 'KMPNativeCoroutinesAsync', '1.0.0-ALPHA-10'    # Swift Concurrency implementation
  pod 'KMPNativeCoroutinesCombine', '1.0.0-ALPHA-10'  # Combine implementation
  pod 'KMPNativeCoroutinesRxSwift', '1.0.0-ALPHA-10'  # RxSwift implementation

end

I'm pretty sure I'm missing smth but I'm not getting what exactly. Should add any configuration to make generated code be accessible in XCode?

rickclephas commented 1 year ago

Hi! The breedsNative property is actually named breeds in ObjC/Swift. The following should work:

createObservable(for: repository.breeds)

Let me know if that isn't the case.

The BreedsRepository is in your shared module, right? In that case you won't need any Gradle related changes.

AlexIach commented 1 year ago

Hey @rickclephas , Thanks for quick response. Using just repository.breeds is working indeed. I don't why I was confused by breedsNative property name. I thought that KSP code generation should rename the property from breeds to breedsNative or it does it only when I specify this inside Gradle by changing suffix?

nativeCoroutines {
.....
}
rickclephas commented 1 year ago

The Native suffix is only used on the Kotlin side since we can't create a property with the same name. This suffix can indeed be customised in your build.gradle.kts file.

However the @ObjcName annotation is used to rename the property to its original name in the ObjC header. This way you don't have to use different property/function names in Swift 😁.

AlexIach commented 1 year ago

Thanks for detailed explanation @rickclephas. I'll close this issue since it's not an issue but just my mistake.