Skip to content

Few words on iOS Android integration

Dmitry Savvinov edited this page Nov 9, 2022 · 1 revision

Android

Integration with Android is fairly trivial and basically boils down to adding packed .jar-files as dependencies to Android build, see here how it is done.

Of course, you have to manually rebuild the .jar-files each time you change something in the multiplatform code, and want to see the changes in Android App.

iOS

Due to some idiosyncrasies of Xcode, integration with iOS is much less trivial.

The biggest conceptual issue is that from one and the same shared Kotlin code you can build multiple frameworks. There are at least three commonly used architectures nowdays (iosArm64 - iPhone Devices, iosX64 - iPhone simulator on Intel Macs, iosSimulatorArm64 - iPhone simulator on M1 Macs), and for each architecture there are debug and release modes of linking framework, yielding at least 6 frameworks.

Even if the input source code is the same, the resulting binary output isn't bit-bit equal in the general case. Using an "incorrect" framework (e.g. framework for simulator when deploying on device) has non-zero chances of working fine for simple apps, but generally, it will lead to hard-to-catch errors and crashes (the bigger the app, the bigger the probability of that).

This was the context. Now come the issues:

  1. There's no metadata on frameworks that would tell something like "hey, this is a debug framework for iOS devices!". This means that you can't point Xcode to the folder which contains all frameworks in the world and let Xcode choose the proper one depending on which build you're currently running. Instead, you have to put frameworks for each debug/release + architecture combination into a separate file tree.

  2. As if it was not enough, Xcode doesn't have separate input fields for each of those combinations. Instead, there's one text field in "Framework Search Paths" (already tweaked in sample project in this repo, read about this tweak more in docs, step #5), which accepts macro-substitutions provided by Xcode like $(CONFIGURATION) (roughly corresponds to debug/release) and $(SDK_NAME) (roughly corresponds to iosArm64/iosX64/iosSimulatorArm64). That's why we have a pretty odd logic in output paths of frameworks: it's to match with asinine Xcode model.

  3. You think that's it? Hell no. "Framework Search Paths" affects only code highlighting in the Xcode editor, and has no effect whatsoever on actual building/linking/deploying the final iOS app. And for some completely obscure to me reasons, there's no second similar text field for the build phase. Instead, you have to add the framework manually through UI in General -> Frameworks, Libraries, and Embedded Content. And for reasons even more obscure, there's no way to specify that this framework is for this $(CONFIGURATION) or that $(SDK_NAME) (maybe I'm just completely clueless and unaware of this functionality in Xcode, and it exists somewhere). It means that if you add debug framework by hand there, and then want to make a release-build, you got to remove the old framework and add a new one. Don't ask me.

How the point #3 works with Kotlin Gradle Plugin? Surely we don't ask people to tweak list of Frameworks each time they want to build a release build or switch from the simulator to the iPhone.

The trick is that we insert in the Xcode build phases invocation of a special Gradle task (see steps #1-4 here). Because this task is launched from Xcode build, the environment will contain various necessary inputs, from which we're able to derive two things: a) which framework Kotlin should provide for this build b) where Xcode will later look for frameworks. Then, this task literally copies the needed Framework to the temporary Xcode directory, which it will use for assembling the runtime environment.

It's impossible to do this proactively, because the paths are not known in advance before the build

So, the possible next improvement step for the Barebones Kotlin Multiplatform would be to provide a semantically similar script (without Gradle, of course), and insert an invocation of that script into the Xcode build, just as in the real world we insert the invocation of Gradle Task.

Reference: sources of the embedAndSignAppleFrameworkForXcode in Kotlin Gradle Plugin