Hi! My team is currently using Kotlin for a projec...
# server
j
Hi! My team is currently using Kotlin for a project but now we want it to be callable from Java. We bet for Kotlin initially because of how it integrated with it, but we’re now having some issues while calling suspend functions. Our objective is for our Java developers to have as less friction as possible by calling our library, but the options we’re finding are not as straight forward: 1. We can create a java utils package and create a
Continuation
implementation 2. We could change the Kotlin code on suspend functions to use things like
GlobalScope
to return
CompletableFuture
but I’m not sure if this is the best practice (or it doesn’t look like it is for me) Do you know what is commonly the best practice for this case? 🤔 Not sure if this is the place for the question, so let me know and I’ll move the message 🙂 . Have a great week!
p
When we faced a similar situation, we created wrappers for our kotlin/ coroutines implementations that converted the API to RxJava. So for example we would go from
fun suspend doSomething(input: String): Output)
and wrap with
fun doSomething(input:String): Single<Output> = rxSingle { wrapped.doSomething(input) }
. Although Rx might not be how you want to expose your API, it does provide a java friendly way to handle this.
you could use another library to wrap, I guess it would depend on what library your java users would be familiar with, but similar solutions are available for other libraries
j
yeah we were trying the same but wrapping with GlobalScope, is there any disadvantage that you know about it? 😮 I read somewhere it’s not memory safe but I’m not that expert in Kotlin sadly 😆
p
the advantage of using another library is that java devs would be familiar with those already, rather than having to learn something new
also you would end up having to support your java implementation of coroutines
j
yeah probably we will go that way 🤔 . Thanks a lot!!
e
In my multiplatform networking library I'm using something on the line of
Copy code
expect class MyDeferred<T>

// JS
actual typealias MyDeferred<T> = Promise<T>

// JVM
actual typealias MyDeferred<T> = CompletableFuture<T>
Then, the service proxy (a wrapper for the internal suspending service) uses
Copy code
// JS
CoroutineScope.promise()

// JVM
CoroutineScope.future()
This allow working on common code for the most part, having a single
MyDeferred
type for all platform. You can use extension functions to add stuff to
MyDeferred
.
🙌 1
Ops, sent too early, will complete
Completed
Or you can even make
MyDeferred
a wrapper class. I think I'll move to that direction
Btw, beware of https://youtrack.jetbrains.com/issue/KT-21846 Just encountered an issue with K2.
j
After researching today I think I’m going to build a custom class level annotation to detect suspend functions and generate the wrappers (java async libraries or just CompletableFuture) using KSP. I’ll let you know if it’s impossible or not 😆
Update: Finally that didn’t seem to work 😞 . KSP and KotlinPoet have some limitation on how to import my original code (like the body of the function or @context) so I had to drop this idea
p
sounds like an interesting idea, why did you need access to the function body to wrap the function?
j
it’s because the generated function is in /ksp/generated/kotlin but I don’t seem to be able to “copy” the call in that generated file. Like: CompletableFuture.supplyAsync { thisCall() } Also I need to differentiate between CompletableFuture.runAsync (for Unit suspend functions) and supplyAsync (for other types). The main problem I faced is that I can’t import that “thisCall()” function in my generated code, it’s like it’s not writing the imports correctly, but maybe I’m doing something wrong 🤔
p
you can add import in kotlin poet on the filespec, you should be able to import function
and was ksp not able to infer the return types correctly for your functions?
j
yeah I could detect if they were Unit or not so I could use one or another, I think I’m just missing the import part. I’ll let you know if I can handle it 😛 Btw I decided to go with a CompletableFuture wrapper to be generic with Java without depending on other libraries, not sure how good is it as the wrapper is very simple 🤔
oh also there’s the case in which a class contains a mix of suspend and non suspend functions. So I have to keep non suspend functions and do the wrapper for the suspend one
p
I never thought about using koltinpoet and ksp to generate these wrappers, might have to look into it as well 😄
j
because my annotation is class level (to abstract it better)
p
we have very many of those wrapper classes around and we have to maintain all of them
j
Hey @Peter Kievits do you want us to create a repo so we can work on this? I won’t have much time for it but at least you can get an idea and maybe we can have something to help the community 🙂
p
could do, if we're both having this problem, other people might have the same problem 😄
j
nice, I’ll try to have a repo with something to work on top of it by tomorrow ✌️
p
nice one
are you thinking of running this as a gradle plugin?
j
Hmm I haven’t done that before but we could yeah 🙂 . Here’s the repo -> https://github.com/theam/kava Disclaimer: It’s not currently working and needs some debugging, but contributions are more than welcome 😛 @Peter Kievits
p
nice - looks like a good start
it might be a good idea to separate the scanning and replacement function generation, that way we can switch out the replacement parts, e.g. replace with futures, rx, something else
I'll try to spend some time over the next couple of days and raise a pr and you can see what you think
j
Sounds great, thanks 🙂
p
how did you run this? I have some code ready, but would like to test it before raising a pr 😄
j
./gradlew build and it should be ready 🙂 @Peter Kievits
anyways it’s not generating the class, I have to do some debugging because it stopped generating after some changes I did (and didn’t commit along the way so.. xD). You can add logs with the Processor logger and run ./gradlew build --info to see them
e
Injecting myself here as I've seen the project seems to use kotlin-jvm. Is it maybe worth organizing it in a way it's open to multiplatform?
p
the build is working fine, but would be good to trigger the code generation; but maybe will just push my code and raise pr for now
i've not dealt with multi platform much; I guess it would depend on if the dependencies support multi platform
j
oh the code generation happens in the project where it’s used, not in “kava” itself
it happends after
/gradlew build
in the applied project
we’re using jitpack to import dependencies in some projects, maybe you can inject it from gh directly: https://jitpack.io/
e
oh the code generation happens in the project where it’s used
That possibly means a common annotation to generate a
Promise
wrapper (yet again with KPoet) is doable?
j
yeah it should I guess 🤔
✔️ 1
could you guys have a look? Was it possible? 😮
p
I had a look and played around with rx java, then I wanted to push and create a pr and I released I needed to fork first 😄
let me do that and show you what I have
I might see if I have some more time to repackage it as a gradle plugin
let me know what you think
j
awesome work @Peter Kievits 👏 !! I just left some comments 🙂
p
replied to your comments, let me know what you think
j
done! I just approved it, do you want me to merge it?? @Peter Kievits
p
go for it!
j
Merged @Peter Kievits! Thanks a lot 🙂
Also, do you have a twitter account? I wanted to make a quick post and I wanted to mention you too 🙂 @Peter Kievits
p
Appreciate the thought! I am on twitter, but just as a lurker, so worry about it.