The central question every cross-platform team faces is whether to centralize business logic with Kotlin Multiplatform vs Swift Concurrency on iOS and native Kotlin on Android — and how to migrate real production apps safely. This article presents concrete patterns, trade-offs, and step-by-step migration actions for sharing business logic between Android and iOS while keeping performance, testability, and developer velocity in mind.
Why share business logic (and why not)
Sharing business logic reduces duplication of rules, validation, and networking, speeds feature parity, and simplifies QA. However, sharing can introduce interop complexity, binary-size overhead, and dependencies on language ecosystems. Make the choice based on team skills, release cadence, and the surface area of logic that is genuinely platform-agnostic.
High-level decision framework
- Reuse percentage: If >50% of domain and data logic is identical, favor a shared module (Kotlin Multiplatform or codegen). If lower, prefer platform-native with interface-driven sync.
- Team expertise: If the iOS team prefers Swift-first and resists multi-language toolchains, prefer Swift Concurrency + codegen; if teams already use Kotlin or want a single language, prefer KMP.
- Release and debugging needs: If tight platform-specific debugging and profiling are critical, lean native Swift/Kotlin. If coordinated feature releases and consistent behavior are higher priority, lean shared logic.
Pattern 1 — Kotlin Multiplatform (KMP) for domain & data layers
What it looks like
Author domain, business rules, networking, serialization, and repository code in Kotlin in a shared module. Use expect/actual for platform-specific hooks (e.g., file IO, platform time). Publish as a CocoaPods/XCFramework for iOS and a Gradle artifact for Android.
Concrete implementation tips
- Structure code with Clean Architecture: domain → use-cases → repositories → platform adapters. Keep use-cases pure Kotlin.
- Encapsulate side effects: expose suspend functions and Flows at module boundaries; implement platform-specific adapters with expect/actual.
- Interop: generate an XCFramework using Gradle and distribute via CocoaPods or a binary repo; wrap suspend functions in callback-friendly entry points for Swift.
- Testing: run unit tests in JVM for logic and Native for critical interop areas; use common tests where possible.
Pattern 2 — Swift Concurrency for iOS, Kotlin on Android, synchronized via API/Schema
What it looks like
Keep native implementations but generate consistent client models and service stubs with GraphQL (Apollo), OpenAPI, or Protobuf/gRPC. iOS uses Swift Concurrency (async/await), Android uses Kotlin coroutines.
Concrete implementation tips
- Define your canonical API and data models in a schema-first tool (GraphQL or Protobuf) and generate platform-specific code to ensure parity.
- Implement business logic patterns similarly on both platforms (same layer names, same sequence of transformations) to simplify onboarding and tests.
- Use shared CI validation: golden tests, API contract tests, and cross-platform end-to-end tests to ensure behavior parity.
Interop and concurrency trade-offs
- Performance: Native Swift code with Swift Concurrency is extremely performant on iOS; Kotlin/Native has improved, but interop layer overhead and binary size must be measured.
- Debugging: Debugging native Swift is smoother for iOS developers; debugging Kotlin/Native across languages requires familiarity with both toolchains.
- Concurrency semantics: Kotlin coroutines and Swift Concurrency map well conceptually (suspend vs async/await), but bridging suspend to async may require wrappers and careful error propagation to keep stack traces and cancellation semantics clear.
- Memory and threading: Kotlin/Native memory model and freezing constraints may require additional architectural patterns (immutable models, message passing). Swift Concurrency avoids that by staying in native territory.
Concrete migration steps for an existing production app
Follow a staged, low-risk path rather than big-bang rewrites.
- Audit and scope: Identify the exact business rules, validations, and service logic candidates for sharing. Prioritize logic with the most duplication or highest bug rate.
- Extract a thin shared API: Create a clear boundary (interfaces/use-cases) and implement the API first as a set of integration tests in both platforms.
- Prototype and measure: Build a KMP proof-of-concept or a schema-first generated module for one feature. Measure binary size, cold start, and debug experience.
- Bridge layers carefully: For KMP, expose simple callback-friendly or async-compatible entry points; for schema-first, ensure generated code maps well to your domain models.
- Parallel rollout: Deploy the shared logic for a low-risk feature to a subset of users; keep feature toggles to revert if issues arise.
- Extend and harden: Add platform-specific adapters, expand test coverage, and automate packaging in CI (XCFramework upload, Gradle publish). Document debugging and onboarding steps.
Practical patterns to reduce friction
- Use adapter/facade layers so platform code calls thin wrappers rather than working directly with multi-platform APIs.
- Favor immutable DTOs and small mapping layers to avoid memory model issues and simplify unit testing.
- Wrap long-running operations with cancellation-aware tokens; mirror cancellation semantics across Kotlin coroutines and Swift async for predictable UX.
- Use feature flags and telemetry to detect behavioral regressions after migration.
When to choose which approach
- Choose Kotlin Multiplatform when: sharing a large portion of domain/data logic, teams are comfortable with Kotlin, and binary size/interop can be managed.
- Choose Swift Concurrency + schema/codegen when: iOS must remain purely Swift-first, the shared contract is primarily API and models, and you prefer native performance and debugging consistency.
- Hybrid approaches are often best: use KMP for pure business rules and codegen for API/model parity, combining the strengths of both.
Sharing business logic across Android and iOS is not a single technology decision but an architectural one. Kotlin Multiplatform vs Swift Concurrency each offers clear benefits; the right choice depends on team skills, the percentage of reusable logic, and operational constraints.
Conclusion: weigh reuse against developer velocity and observability, prototype early, and migrate incrementally with a clear adapter boundary and test strategy. Ready to evaluate your codebase and create a pragmatic migration plan? Contact the team for a tailored assessment and stepwise roadmap.
