Is it possible to modify the outcome project from ...
# multiplatform
a
Is it possible to modify the outcome project from
<https://kotlinlang.org/docs/native-app-with-c-and-libcurl.html>
to build targeting Android/iOS targets?
e
Do you want to use C and target Android and iOS? What are you going to achieve?
a
Yes exactly
I have a couple of C/C++ libraries that I want to use from within my KMP/CMP app
e
I am also interested on this topic and don’t know the right way to do that. But at least you can add an interface for your library with
expect
methods and put the implementations with
actual
in the android and ios modules. In this case you will have 2 copies of your lib in each module (android and ios).
a
yeah this part should be easy... I'm interested more in the
build.gradle.kts
adjustments that allows for building targeting
NDK
and
Xcode toolchain
🤔
were you able to achieve that?
e
Not tried yet. But I would also appreciate any advise from people who already had experience.
l
You need to target androidNative* and ios* to achieve this. Cinterop will work just like normal. The biggest challenge with androidNative is distribution, but look at how packaging a shared object normally works in android.
a
Any guide/repo example on that? The documentation on that is almost none.
l
I haven't touched this project in a while, since the JNI doesn't tend to change and it accomplished what I needed from it, but here's a sample project. https://github.com/LandryNorris/JniUtils/tree/main/sample
a
I assumed it won’t have anything to do with JNI
l
Also, I would recommend using the library that I wrote for the JNI bindings. It makes setup for the bindings much simpler.
Do you plan to call the android native code from jvm? Or will this be a pure native Android app (using the C++ SDK)?
a
Primarily native, the UI will be Compose and core is Native
If it were for JNI then it’s no big deal as the documentation in that is outstandingly large
l
Compose UI doesn't run on androidNative. You'll need to make JNI bindings to call into your Kotlin/Native code. JVM code can't just call a C function without JNI.
a
Aha so I’ll have to make the glue code bridging both worlds, correct?
l
Yes. The sample I linked to should show how to write the bridge in pure Kotlin.
a
If that’s the case, then why not just build libraries out of Kotlin’s world and just use them in both Android and iOS? 🤔 I thought when I tried the Kotlin Native libcURL example that I’ll be able to inter bridge both
l
You could write the JNI bindings in C if you wanted, then use expect/actual. On iOS, you can just use cinterop to bridge to C. Android has to go through the JNI, though to work between Kotlin/Native and Kotlin/JVM.
a
Why is it such straightforward in iOS with cinterop
Or am I missing something here
l
On iOS, code runs on the metal, whether it's C, Swift, or Kotlin/Native. They can all call into anything the others expose using the C ABI. On Android, code typically runs in the JVM. If you want to run code on the metal, you need to tell it how. That's what the JNI is for.
a
Aha makes sense
I guess there must be something that does the same automated bridging for Kotlin/cinterop/JNI
l
It would be possible to write a gradle plugin to automate the generation, but a good deal of work.
a
Can I use cinterop for iOS and NDK/JNI for Android?
Any idea on how to achieve such structure?
At least for the iOS part
I’m familiar with the JNI part
l
Yes. You'll need to choose whether you want the NDK portion to use C or Kotlin/Native. If C, then write the JNI bindings in C, then create the binding class in Kotlin/JVM. On iOS, use cinterop and create a binding class in Kotlin/Native. Then use expect/actual so you can call from common code. For simple bindings to a C library, this may be best. If you want to use Kotlin for all of it, you can use cinterop for both androidNative and iOS. This would be better if there's any complex logic on the NDK side, since you can share code with iOS.
a
I would prefer using androidNative and build the JNI glue wrappers, however can’t find a credible source on doing so
Haven’t checked your repo yet tho
I’m missing the part where I building the Kotlin Native stuff using NDK toolchain
l
The gradle plugin handles setting up the NDK toolchain. You just need to declare the androidNative targets (they're normal Kotlin/Native targets, so cinterop works largely the same). This will build a binary in the build folder. You'll need to set up a gradle copy task to put the binary somewhere the aar or apk can find it.
I should probably write a Readme in the sample explaining how to set that up.
a
Oh
Would highly appreciate it
Or even a small snippet from build.gradle as a starting point
l
The important part for androidNative is to have a section like this in the build.gradle.kts telling it where to put the binary. This creates a task that you can either call manually or set up with other tasks
Copy code
tasks {
    val prepareAndroidNdkSo by creating {
        dependsOn(build)
        val debugArm32SoFolder = File(buildDir, "bin/androidNativeArm32/debugShared")
        val jniArm32Folder = File(projectDir, "src/androidMain/jniLibs/armeabi-v7a")
        val debugArm64SoFolder = File(buildDir, "bin/androidNativeArm64/debugShared")
        val jniArm64Folder = File(projectDir, "src/androidMain/jniLibs/arm64-v8a")

        val debugX86SoFolder = File(buildDir, "bin/androidNativeX86/debugShared")
        val jniX86Folder = File(projectDir, "src/androidMain/jniLibs/x86")
        val debugX64SoFolder = File(buildDir, "bin/androidNativeX64/debugShared")
        val jniX64Folder = File(projectDir, "src/androidMain/jniLibs/x86_64")

        doLast {
            copy {
                from(debugArm32SoFolder)
                into(jniArm32Folder)
                include("*.so")
            }
            copy {
                from(debugArm64SoFolder)
                into(jniArm64Folder)
                include("*.so")
            }
            copy {
                from(debugX86SoFolder)
                into(jniX86Folder)
                include("*.so")
            }
            copy {
                from(debugX64SoFolder)
                into(jniX64Folder)
                include("*.so")
            }
        }
    }
}