Hi everyone! I would like to define a kotlin intef...
# swift-export
p
Hi everyone! I would like to define a kotlin inteface containing suspend functions and Flows. Is this possible currently? From the docs it's not immediately obvious since it mentions some coroutine/flow interoperability and cross-language sub-typing but it doesn't show an example of coroutines/flows inside a kotlin interface implemented by Swift code. I tried it with some simple example:
Copy code
// kotlin interface
interface UserRepository {
    val signedInUser: Flow<String>
    suspend fun signInUser(user: String)
}

// generated protocol
public protocol UserRepository: KotlinRuntime.KotlinBase, ExportedKotlinPackages.com.example.swift_export._UserRepository {
    var signedInUser: any KotlinCoroutineSupport.KotlinTypedFlow<Swift.String> {
        get
    }
    func signInUser(
        user: Swift.String
    ) async throws -> Swift.Void
}
Let's say on android I would just implement this with a
MutableStateFlow
, how could I implement this in swift. How can I create a
KotlinTypedFlow
from the Swift side? What should I pay attention to in terms of cancellation? Will throwing a
CancellationError
from swift cancel the coroutine in kotlin?
👀 1
g
I tested this exact scenario in a personal SwiftExport sample (Kotlin 2.5.0-Beta1-43 + kotlinx-coroutines-core 1.10.2), short answer: not possible yet, and it’s not Flow-specific, no user-defined Kotlin interface can be implemented in Swift today, even one with just a suspend fun. Every generated protocol requires the conforming type to inherit KotlinRuntime.KotlinBase. You can declare a class that satisfies it, but you can never instantiate it, KotlinBase.init() is NS_UNAVAILABLE, and the only real initializer needs a pointer to an already-existing native Kotlin object. Concrete classes get a generated public allocator, interfaces don’t, since there’s nothing on the Kotlin side to bind to. Regarding cancellation, it works cleanly in the direction that’s actually usable today. The current workaround is keeping Kotlin as the owner and implementer, e.g. a MutableStateFlow, and expose plain non-Flow functions for Swift to push updates into it, Swift calls the setter and reads the same flow back via asAsyncSequence. Works well today. github.com/GuilhE/SwiftExport-Playground
p
@Guilherme Delgado Hmm I also created a tryout using AI for examples and it was able to implement the interface with suspend functions and flows no-problem. It was also able to instantiate the class no-problem. Here's the repo for reference: github.com/AradiPatrik/swift_export_coroutine_flow_in_interface_demo I am more interested in having some sort of guidance from jetbrains, because although I am able to prompt AI to implement some workable solution it seems like it's relying on mostly undocumented behavior. I couldn't find any docs authored by jetbrains that would give guidance about this use-case, so that is why I'm asking here.
👍 1
g
The trick I was missing was that Swift can’t subclass KotlinBase directly, I hadn’t noticed that part of the docs (open class SwiftBase) and was only trying KotlinBase directly, which is why I said it didn’t work. But I still can’t get this working end to end on 2.5.0-Beta1-73, it crashes every time I pass the Swift object into a real Kotlin function, ClassCastException, Kotlin can’t cast it to the interface type. Tried the exact same code on 2.4.20 and it works fine there, no crash. So looks like a regression somewhere between 2.4.20 and the 2.5.0-Beta1-73.
e
@Artem Olkov would you be able to help out here? Thanks!
a
Yes, this thread is on my todo list 🙂
🙏 2
kodee walking 1
p
Hi @Artem Olkov, just following up on this one. Any chance you’ve had a moment to look at it? Thanks! 🙏
a
Hello there 🙂 Sorry for the long reply, we had a rush to the 2.5.0 branching. The release is packed with features, stay tuned for the News! For your case the implementation is quite straight forward
Copy code
final class SwiftUserRepository: KotlinBase, UserRepository {
    private let user = mutableStateFlow(initial: "")

    var signedInUser: any KotlinTypedFlow<String> { user }

    func signInUser(user: String) async throws {
        try await Task.sleep(for: .milliseconds(300))
        self.user.value = user
    }
}
The only thing that is needed on the kotlin side - is this helper, to specify the generic type that will be used, as we Swift Export don't support generics ATM 🙂
Copy code
fun mutableStateFlow(initial: String): MutableStateFlow<String> = MutableStateFlow(initial)
This snippet works with Kotlin 2.5.0-Beta2 and newer. For earlier kotlins one would have to do the trick with an empty class definition to inherit from it.
kodee loving 1
🙏 1
Will throwing a
CancellationError
from swift cancel the coroutine in kotlin
Yes, throwing a Cancellation from
signInUser
would cancell kotlin counter part
🙏 1
g
@Artem Olkov it makes this much simpler and clean 👌
kodee intensifies 1