Jemo
07/12/2024, 4:21 PMsomeResult.onSuccess { }.onFailure { }
Jemo
07/12/2024, 4:21 PMTadeas Kriz
07/12/2024, 4:22 PMonSuccess {}.onFailure {}
DSL. That way your iOS folks will be able to use it.Jemo
07/12/2024, 4:39 PMTadeas Kriz
07/12/2024, 4:48 PMsrc/iosMain/swift
(or even src/commonMain/swift
) and you have access to all the exported Kotlin types. The one thing to keep in mind is that default Swift visibility is internal
, so you need to make stuff public
if it should be visible from outside of the Kotlin framework.Jemo
07/12/2024, 4:52 PMJemo
07/13/2024, 9:30 AMJemo
07/13/2024, 9:31 AM./gradlew spmDevBuild -PspmBuildTargets=ios_simulator_arm64
and then copy paste generated framework inside xcode projectTadeas Kriz
07/13/2024, 2:26 PMJemo
07/15/2024, 6:05 AMimport Foundation
public struct TestResource {
func test() {
print("I'm resource")
}
}
Jemo
07/15/2024, 6:07 AMTestResource
but now I can't test it cause spmDevBuild
fails with noswiftintefaces found errorRonald van D
07/15/2024, 8:19 AMsealed class KmpResult<out T : Any> {
class Success<out T : Any>(val data: T) : KmpResult<T>()
class Error(val error: KmpException) : KmpResult<Nothing>()
..........
fun onSuccess(action: (T) -> Unit): KmpResult<T> {
if (this is Success<T>) action(this.data)
return this
}
fun onError(action: (KmpException) -> Unit): KmpResult<T> {
if (this is Error) action(this.error)
return this
}
......
}
In our iOS project we can then call this like your original question
result
.onError { error in }
.onSuccess {data in }
Jemo
07/15/2024, 8:33 AMonSuccess {data in }
is type of data
available at compile time?Ronald van D
07/15/2024, 8:56 AMJemo
07/15/2024, 9:18 AMTadeas Kriz
07/15/2024, 1:53 PMskie {
build {
enableSwiftLibraryEvolution.set(true)
}
}
Lastly I'd caution against using SPM as it usually leads to longer compilation times (especially if you're using XCFramework).Jemo
07/15/2024, 2:20 PMenableSwiftLibraryEvolution.set(true)
fixed all issues,
swift files are also included in final XCFramework.
Thanks a lot 🎉Jemo
07/15/2024, 2:21 PMTadeas Kriz
07/15/2024, 2:21 PMJemo
07/15/2024, 2:21 PMitshan
07/16/2024, 3:56 AMFor-in loop requires 'any Kotlinx_coroutines_coreFlow' to conform to 'AsyncSequence'
just add implementation(“co.touchlab.skieruntime kotlin${libs.versions.touchlab.skie.get()}“) to fix that.Tadeas Kriz
07/16/2024, 4:13 AMTadeas Kriz
07/16/2024, 4:13 AMitshan
07/16/2024, 4:14 AMkotlin 1.9.22
kotlinx-coroutines 1.8.1
touchlab-skie 0.6.1
Tadeas Kriz
07/16/2024, 4:14 AMTadeas Kriz
07/16/2024, 4:14 AMitshan
07/16/2024, 4:14 AMitshan
07/16/2024, 4:15 AMTadeas Kriz
07/16/2024, 4:15 AMTadeas Kriz
07/16/2024, 4:15 AMTadeas Kriz
07/16/2024, 4:15 AMTadeas Kriz
07/16/2024, 4:16 AMitshan
07/16/2024, 4:26 AMimport co.touchlab.skie.configuration.DefaultArgumentInterop
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.kover)
alias(libs.plugins.touchlab.skie)
alias(libs.plugins.cash.sqldelight)
alias(libs.plugins.touchlab.kmmbridge)
`maven-publish`
}
/**
* will update to for createSwiftPackage as we
*/
version = "1.0-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
kotlin {
jvm()
listOf(
// macosX64(),
macosArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
export(libs.androidx.lifecycle.viewmodel)
export(libs.koin.core)
isStatic = true
export(libs.napier)
}
}
sourceSets {
commonMain.dependencies {
// put your Multiplatform dependencies here
implementation(libs.kotlinx.serialization.json)
implementation(libs.bundles.ktor.client)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.tddworks.openai.client.core)
// view model
api(libs.androidx.lifecycle.viewmodel)
// Koin
api(libs.koin.core)
implementation(libs.koin.composeVM)
// SQLDelight
//<https://hyperskill.org/learn/step/33432#install-and-configure-sqldelight-async-extension>
implementation(libs.app.cash.sqldelight.async.extensions)
implementation(libs.app.cash.sqldelight.coroutines.extensions)
// logging
api(libs.napier)
// testing
implementation(libs.koin.test)
}
commonTest.dependencies {
implementation(libs.ktor.client.mock)
}
appleMain.dependencies {
implementation(libs.app.cash.sqldelight.native.driver)
implementation(libs.tddworks.openai.client.darwin)
// implementation("co.touchlab.skie:runtime-kotlin:${libs.versions.touchlab.skie.get()}")
}
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
implementation(libs.app.cash.sqldelight.sqlite.driver)
}
jvmTest.dependencies {
implementation(project.dependencies.platform(libs.junit.bom))
implementation(libs.bundles.jvm.test)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.app.cash.turbine)
implementation(libs.koin.test.junit5)
}
}
}
addGithubPackagesRepository() // <- Add the GitHub Packages repo
kmmbridge {
/**
* reference: <https://kmmbridge.touchlab.co/docs/artifacts/MAVEN_REPO_ARTIFACTS#github-packages>
* In kmmbridge, notice mavenPublishArtifacts() tells the plugin to push KMMBridge artifacts to a Maven repo. You then need to define a repo. Rather than do everything manually, you can just call addGithubPackagesRepository(), which will add the correct repo given parameters that are passed in from GitHub Actions.
*/
mavenPublishArtifacts()
// spm()
spm {
swiftToolsVersion = "5.9"
platforms {
iOS("14")
macOS("13")
}
}
}
skie {
build {
enableSwiftLibraryEvolution.set(true)
}
features {
group {
DefaultArgumentInterop.Enabled(true) // or false
}
enableSwiftUIObservingPreview = true
}
}
tasks {
named<Test>("jvmTest") {
useJUnitPlatform()
}
}
itshan
07/16/2024, 4:27 AM// implementation("co.touchlab.skie:runtime-kotlin:${libs.versions.touchlab.skie.get()}")
itshan
07/16/2024, 4:30 AMTadeas Kriz
07/16/2024, 4:35 AMitshan
07/16/2024, 4:43 AM// implementation("co.touchlab.skie:runtime-kotlin:${libs.versions.touchlab.skie.get()}")
itshan
07/16/2024, 4:44 AMimplementation("co.touchlab.skie:runtime-kotlin:${libs.versions.touchlab.skie.get()}")
which download this version
https://repo.maven.apache.org/maven2/co/touchlab/skie/runtime-kotlin-macosarm64__kgp_1.9.20/0.8.2/runtime-kotlin-macosarm64__kgp_1.9.20-0.8.2.klibitshan
07/16/2024, 4:44 AMitshan
07/16/2024, 6:51 AMFilip Dolník
07/16/2024, 6:53 AMitshan
07/16/2024, 9:37 AMFilip Dolník
07/26/2024, 4:22 PMMichal Klimczak
12/31/2024, 2:58 PMAny?
in the type.
sealed interface Either<out Data, out Failure> {
data class Success<out Data>(val data: Data) : Either<Data, Nothing>
data class Failure<out Failure>(val error: Failure) : Either<Nothing, Failure>
}
fun <Data, FailureIn, FailureOut> Either<Data, FailureIn>.mapFailure(transform: (FailureIn) -> FailureOut): Either<Data, FailureOut> = when (this) {
is Either.Failure -> Either.Failure(transform(error))
is Either.Success -> Either.Success(data)
}
And then in swift
let result = try! await foo.bar().mapFailure { failure in
failure // this is Any?
}
Generated code is (seems to lose all type info)
extension shared.Either {
//...
public func mapFailure(transform: @escaping (Any?) -> Any?) -> shared.Either {
return shared.EitherKt.mapFailure(self, transform: transform)
}
}
Of course I do have
skie {
build {
enableSwiftLibraryEvolution.set(true)
}
}
Although this is on my shared
modules, whereas the Either type itself is declared on another module, not sure if that's relevant. I am generating xcframework and static=true.
Any ideas what I might be doing wrong?
Can you show how the swift wrapper generated by SKIE looked in your case?Michal Klimczak
01/02/2025, 7:45 AMMichal Klimczak
01/02/2025, 2:34 PMinterface -> class
- sealed class Either<out Data, out Failure>
2. After having read Kevin's article I realized that out
variance should work and this is what we have on such Either / Result type, so we're good here (Either<out Data, out Failure>
)
3. Standard Kotlin's Result<T>
will not work, because it's a value
/ inline
class which has limitations of its own
4. In Kotlin I had defined extension functions like fun <Data, Failure> Either<Data, Failure>.onSuccess(action: (Data) -> Unit): Either<Data, Failure>
. This also strips the type in obj-c. Simply making them regular class functions (fun onSuccess(action: (Data) -> Unit): Either<Data, Failure>
) retains the type and enables what Ronald has proposed above with his KmpResult.
5. However there are still functions like fun <DataOut> map(transform: (Data) -> DataOut): Either<DataOut, Failure>
where the DataOut
type is lost. And it cannot be handled with some clever extensions function on the swift side, because one cannot access generic types via an extension function on obj-c type. So no extension functions for my Either
type at all 😞. What can be done about it:
a. Wrap the whole Either into some SwiftEither and handle the type transformations there.
b. Use top-level swift function instead of extensions function (much like SKIE does it with stuff like skie(result).map...
)
c. Just force cast in those situations
d. ^ I don't really like any of these. Am wondering if Kotlin 2.1.0 experimental interop does anything helpful here.
6. And swift bundling is not any magical instrument that enables some special interop - it only enabled what would already be possible in Swift - its purpose is different.Filip Dolník
01/06/2025, 10:13 AMa. Use top-level swift function instead of extensions function (much like SKIE does it with stuff like)skie(result).map...
b. Just force cast in those situations
c. ^ I don’t really like any of these. Am wondering if Kotlin 2.1.0 experimental interop does anything helpful here.
yeah, SKIE has to use the
skie()
function to workaround this limitation. We haven’t found a better way around it for suspend functions. There is a solution it that would work in your case by utilizing Obj-C to Swift bridging like we do for Flows or enums but that is quite complex to setup properly.
The Swift Export will not solve that in the foreseeable future as generics are quite far in the roadmap (and it will take a while before it’s production ready)