Commentary on Jacob’s Swift for Android vs. KMP Article
Swift for Android is here. It is going to be a tough choice in a few years to decide which language to pick for multi-platform development but as of today Kotlin Multiplatform(KMP) is the clear winner
I recently had the privilege of proofreading and sharing my thoughts with Jacob about his Swift for Android vs. Kotlin Multiplatform article before it was published.
Jacob’s done a great job fairly covering both KMP and Swift for Android implementations and showcasing their respective pros and cons! If you haven’t read his article go ahead and read it here. While you’re at it don’t forget to subscribe to Jacob’s substack!!!
I thought I’d share my comments with the readers of The Mobile Engineer as well.
I mainly can comment on the Kotlin Multiplatform (KMP) side of things, as this is what we use at UpKeep and what I use in my personal projects. I’m still working on Part 2 and 3 of the architectural overview article that I published previously: Mobile Architecture at UpKeep, Part 1. They will cover more in depth the usage of KMP that we have in our iOS and Android apps.
On Platform-Specific Code and Decoupling
Jacob highlighted KMP’s expect/actual mechanism for injecting platform-specific code.
Kotlin Multiplatform has a very neat way of neatly injecting platform-specific code. First, you declare a provider file with an expect fun.
Now the actual fun begins. For each platform you’re implementing on, you set up the expect fun’s implementation: “actual fun”. Here, we can inject Ktor’s built-in HTTP client for Darwin platforms.
While neat, I lean toward a more decoupled approach using Dependency Injection (DI).
Instead of expect/actual, which keeps the platform-specific code declaration in the common module, we favor having the common KMP module depend on a simple interface. The platform-specific code (in the Android or iOS codebase) then implements that interface, and we inject that concrete object into the KMP class.
This approach offers a few key benefits:
Decoupling: It keeps the shared KMP code pure and agnostic, free from any platform-specific boilerplate required by expect/actual.
Clean Architecture: It adheres more closely to standard DI patterns, avoiding what some might view as “hacks or crutches” when platform capabilities are involved.
Modularization: The Single Umbrella Framework
Jacob pointed out a major pain point for KMP users: having to expose the shared logic as a single umbrella framework to the platform apps (well, iOS in particular).
Idiomatically, the KMP shared library is called “Common”. Or, sometimes, “Shared”. You can modularise the library as much as you like, but you will need to expose this to the platform apps as a single umbrella framework. This is pretty annoying for namespacing, but you stop noticing it pretty fast*.
https://blog.jacobstechtavern.com/i/177580473/calling-kotlin-from-ios
This is absolutely true, and it creates annoying namespacing challenges that you eventually just have to live with.
At UpKeep, we ran into this exact issue when setting up our KMP integration. The reason for this single module constraint usually boils down to how fundamental types (like String, Int, Array or the fundamental custom types you share) are handled. If you export multiple modules that all depend on these foundational types, the platform (specifically Swift/Obj-C) can see them as incompatible, distinct types across the different modules.
However, there is good news: the KMP team is working on solving this. There is a beta version of direct Swift export for KMP that appears to resolve this issue, which should allow us to export multiple, separate modules to Swift soon.
The SKIE Debate and Interoperability
The article mentioned SKIE (Swift Kotlin Interface Enhancer) as a way to smooth out rough edges around things like suspend functions and sealed classes, making them more idiomatic Swift.
When you’re running a long-term KMP project, you might want to use SKIE (Swift Kotlin Interface Enhancer). There’s a few rough edges around things like suspend functions, interfaces, and sealed classes which the library sands down into idiomatic Swift signatures.
We went back and forth on adopting SKIE, but ultimately, we decided against it. Instead of relying on an external tool to bridge the gap, we manage the interoperability ourselves. On the iOS side, we use simple wrapper classes around the KMP interfaces to convert them into familiar platform types like RxSwift Observables/Singles. This gives us more explicit control over the final developer experience.
Headless Logic and Testing
I loved Jacob’s idea of creating a little CLI app target to interact with and test the multi-platform module outside of the UI. This is a neat way to handle testing and interaction!
https://blog.jacobstechtavern.com/i/177580473/working-with-headless-libraries
I’ve long been an advocate for headless business logic (logic that runs without any UI dependency), even before the multiplatform movement. Now, it makes even more sense. Personally, I achieve similar benefits by running real staging API calls directly from my unit and integration tests instead of relying on a separate CLI.
Architectural Boundaries: ViewModels
A great question Jacob posed was: “Where do the view models go?”
This is the single most controversial point of debate when implementing a multiplatform solution. Where do we put the view models? “Shared business logic” unambiguously includes networking, caching, analytics, models, processing, queues, config, and most of your services. But view models (and, depending on your architecture, perhaps interactors, presenters, and navigators) could go either way. This is a double-edged sword.
https://blog.jacobstechtavern.com/i/177580473/where-do-the-view-models-go
In my view, View Models (VMs) are a platform-specific concept. If they are simply plain old objects used purely for managing UI state and exposing data from the shared layer (without containing business logic), they belong in the platform codebase (Android/iOS).
For sharing higher-level architecture components, like Interactors or Presenters, the debate continues. We don’t share these at UpKeep yet, but I’m actively considering porting some or all of a framework like RIBs to KMP for that exact reason. I hope to write an article about that process someday!
The Mental Overhead of IDE Context Switching
Haha, Jacob nailed naming the IDE context switching overhead with the title The big IDE fuckfest lol
I doubt “the big IDE crapfest” would sufficiently convey the overwhelming mental overhead that comes with bouncing between 2 instances of Android Studio (or IntelliJ) and 2 instances of Xcode, for 2 different implementations of the same project.
I understand that this is a huge pain for some developers but IMO - I don’t mind because I actually bounce between three IDEs on my mobile projects: Xcode (for iOS), Android Studio (for Android), and IntelliJ IDEA (for KMP/Kotlin code)! (oh and + Cursor these days for LLM code generation). While the overhead is real, the value gained from sharing the core logic often outweighs the annoyance. Plus, I personally appreciate the mental split - ok, I’m in Xcode, now I’m working on the iOS side, next I’m in IntelliJ IDEA, now I’m working on the shared business logic with no UI overhead, etc.
Team Structure: Core vs. Full-Stack
Finally, Jacob noted that some KMP shops use “core engineers” (business logic) vs. “design engineers” (UI) model, which requires careful API contract coordination.
At UpKeep, we’ve taken a different approach. We are a smaller team—three iOS and three Android engineers, six total—and we intentionally encourage a “full-stack” approach. We make iOS developers learn KMP/Kotlin and Android developers become familiar with Swift and iOS compilation issues. This ensures that every developer has a stake in the shared logic and can troubleshoot issues across all parts of the mobile stack.
Conclusion
Jacob did a great job covering and comparing Swift for Android and KMP. Currently, Swift for Android is clearly not ready for primetime, lacking the polish and tooling that KMP provides. But, it will be interesting to see how they stack up against each other in a few years. My personal preference remains KMP, as I find Kotlin to be a more “sane” and unsurprising (which is a good thing) language compared to Swift, so I will continue to use it for multi-platform development for the time being.








