What are the current approaches that one can use t...
# kotlin-native
j
What are the current approaches that one can use to generate a .framework, from kotlin/native, and distribute it to be consumed by third party iOS developers (with objc/swift)? Is the podspec, generated by the cocoapods plugin, a good fit for this scenario? Or would it be better to have an xcode project whose sole purpose is to generate the final .framework, assuming that it will be built entirely with kotlin?
k
The K/N compiler can already generate a framework
Or you can use pods
j
thanks Kris! I’m confused with the toolchain, still haven’t wrapped my head around it, the kotlin multiplatform gradle plugin is quite something 🙂 One such confusion is the type of archs supported by the generated framework, does the plugin allow to build a .framework supporting multiple targets simultaneously or do we have to build for each target and later on merge them with, for example, lipo (I’m referring to iOS arch types, not multiple platform targets)?
t
https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries see section “building universal frameworks” (indeed using lipo)
j
@Tijl thank you!
j
@Kris Wong this posts go over the exact situation we’re currently in, thank you!
🍻 1
t
I have been researching this topic for a while myself. I currently have an MP module shared between 2 Android and 3 iOS projects, using the cocoapods plugin on the iOS side. The main issue is that to make it more Swift friendly, we need just a small Swift layer as part of the module - callback pairs to Swift Result types, type aliases for the typedefs that get lost, extensions on the Objective-C types and converting the Companion methods back into Swift static methods. Since we cannot add Swift to the MP project itself, we end up with duplicated code in the iOS projects. I have looked at writing a wrapper framework, but iOS does support what they term ‘umbrella’ frameworks. Now that Swift Package Manager (since Xcode 12 / Swift 5.3) supports binary frameworks, that seems like the natural destination. But it only works with xcframeworks, which is fine, just need some scripts to generate them (not natively supported in Xcode). Important bonus feature: Kotlin Native needs to support Mac Catalyst as a target - it appears more emphasis is being placed on that, and it would fit in nicely to the xcframework as well. Summary: I think SPM is the way forward: if the tools allowed swift code in the MP module, and there was an SPM plugin which compiled the Kotlin code into an xcframework combined with the Swift source in a Swift Package, that would be a dream. It would require Xcode 12+, but it can still target down to iOS 8/9, depending on the APIs used.
j
thanks for the insights @tylerwilson! I’m not acquainted with SPM, does that mean that objc frameworks can’t be publish to SPM? As for distributing the Kotlin/Native .framework directly through cocoapods, I’m wondering that it is achievable by pre-compiling/generating the .framework and creating a plain simple podspec that points to the generated fat framework instead of using the podspec that the kotlin cocoapods plugin generated, right?
t
Not directly. They have to built and packaged as xcframeworks. You run xcodebuild for each supported target, then xcodebuild-createxcframework to combine them into one xcframework that is usable from anywhere.
And yes, cocoapods plus fat framework ought to be just fine for the pure Kotlin case.
And again, though the exported Objective-C header/framework works quite well with Swift, I expect you will quickly want that little Swift ‘shim’ to make it better. And that is where things get messy.
j
The main use case we’re looking for is to consume the kotlin/native .framework from objc and not swift which means that for now we can delay SPM. I’m guessing we can’t leverage the cocoapods plugin feature to generate the cinterop defs from pod dependencies and also generate a fat framework from the cocoapods plugin but now that I’m thinking about it, that wouldn’t be possible because whenever there’s pod dependencies set on the cocoapods plugin, that has to be compiled from xcode in order to correctly bring the pod dependencies 🤔
k
to be clear, it needs to be compiled from Xcode once - each time the dependencies are changed
👍 1
t
That was tricky to follow, but if I understand it correctly: the current cocoapods plugin allows you to both consume pods and build as one too: you may simply have to include those dependent pods in the clients that use your pod…
Of note too perhaps: I was looking through touchlabs KaMP Kit, and they have their own version of a cocoapods plugin. It was not obvious what is different there, but perhaps something to look into.
j
Sorry Tyler, let me try to clarify, what I was trying to say is that the kotlin cocoapods plugin offers : • the ability to generate a podspec file that references the .framework generated from the kotlin/native library. This podspec can be directly used from an xcode project (as we typically see on the samples • the ability to add pod dependencies so that we can consume those APIs directly from kotlin/native (see https://github.com/JetBrains/kotlin-native/blob/master/samples/cocoapods/kotlin-library/build.gradle.kts#L35). This has the benefit that the cocoapods plugin itself will generate the cinterop defs so that the APIs can be consumed from kotlin code. Let’s say that if my kotlin/native framework depends on a pod dependency (AFNetwork, for example) and I wanted to distribute my kotlin/native generated fat framework through cocoapods so that a third party could consume it, perhaps the podspec used to distribute the fat framework would have to define which dependencies it has and that would be enough ?
I’m sensing that I’m mixing concepts here which is causing me confusion
t
i have not used dependencies from my KMP module, but my understanding is that if you put the dependencies to AFNetworking or others in your gradle, then the generated podspec will include those so that clients of your pod will do the right thing.
j
that makes sense! Reading https://kotlinlang.org/docs/reference/native/cocoapods.html#interoperability it states:
To use these dependencies from a Kotlin code, import a package 
cocoapods.<library-name>
so perhaps some fiddling needs to be done in order to be able to reference the API from AFNetworking from the kotlin/native iOS code when the fat framework is manually generated 🤔
t
When you generate the framework - fat or not - the Objective-C header should only include your Kotlin interfaces, not any pods you pulled in yourself. The client using your pod will have be using AFNetworking as well, so they will have access to the same classes you are using in Kotlin…
k
that's only 2 legs of the stool. the 3rd is running cinterop on the pod dependencies to generate bindings for consumption in your iOS/macOS target in your MPP
j
exactly, my idea would be to leverage the cocoapods plugin and allow it to generate the cinterop defs from the pod dependencies so that we didn’t have to manually generate it and still be able to manually generate the fat framework to distribute it over cocoapods
t
Right, I never imported a dependent pod myself that way. I like to keep my module as ‘pure’ as possible. Side note: instead of AFNetworking, I would encourage you to take a look at Ktor - does all the networking you need and works on all the main Kotlin platforms too. 🙂
j
Yeah, I’ve just used AFNetwork as an example since that’s the one being used on the sample I’ve linked above
k
the only gotcha I am aware of is this: https://youtrack.jetbrains.com/issue/KT-38749
j
I’m feeling the best bet is indeed to just manually generate the cinterop defs and manually generate the fat framework, this seems to be most frictionless approach currently
k
that seems like an odd perspective -- let's remove all the automated tools and handle it all manually for the least friction?
t
agreed. there are some example scripts out there that will generate a fat fromework as a gradle task. and as mentioned the cinterop ought to be generated for you too (I thought the coacopods plugin would perform that step for you).
j
english not being my first language is indeed a barrier to the message I want to pass 😄 I’m in no way advocating that doing all of this manually is the way forward! it seemed to me (apparently wrongly!) it wouldn’t be possible to use the cocoapods plugin to generate the cinterop for us and at the same time generating the fat framework because the way that the APIs, from pod dependencies , have a specific way of being referenced when the cinterop is automatically generated Nonetheless, what you’ve shared is really helpful and perhaps I’m now in a stage that I’m better of giving it a try and see the outcomes to internalize this information
t
Good luck! Let us know how it goes.
r
I was looking through touchlabs KaMP Kit, and they have their own version of a cocoapods plugin. It was not obvious what is different there,
Primarily it’s for configuration. The official plugin creates its framework internally. The Touchlab fork exposes a closure where you can configure it yourself, so you can toggle
isStatic
, explicitly export dependencies, etc.