
Ever tried calling get<T>() from Swift in your KMP project and hit a mysterious UnsupportedOperationException: reified inline function can’t be called from Swift?
Yeah, same here. Let’s fix that once and for all: cleanly, safely, and without leaking half your Kotlin architecture into Swift.
🚨 The Problem
You’re building a Kotlin Multiplatform app with shared ViewModels managed by Koin. On Android, everything’s smooth:
val viewModel = get<SomeViewModel>()
But when you try the same from Swift…
let viewModel = KoinKt.get(SomeViewModel.self)
Boom 💥 — runtime crash:
UnsupportedOperationException: Reified inline functions can’t be called from Swift
That’s because reified generics rely on Kotlin’s compiler magic, which disappears once you cross into Swift/ObjC land.
From Swift, there’s no generic type information for Koin to resolve.
So what do many devs do?
They pass all constructor dependencies from Swift manually. It works, but it’s ugly and leaky:
- Swift has to know about repositories, dispatchers, and more.
- It defeats the purpose of keeping DI logic in Kotlin.
We want a clean solution:
- ✅ Swift only passes runtime parameters (like an
id), - ✅ Koin wires the rest automatically,
- ✅ No reified generics involved.
🧠 The Idea
Instead of calling get<T>() from Swift, we expose tiny Kotlin functions that internally use get { parametersOf(...) }.
Then, from Swift, we resolve them by calling a small Kotlin helper singleton that exposes safe functions for each ViewModel.
The result: Swift stays clean and type-safe, Koin stays in control.
🧩 Step 1: Define Your Koin Module
// commonMain
factory { (id: Long) ->
SomeViewModelWithId(
id = id,
repository = get(),
)
}
single { SomeViewModel(get(), get()) }
// …other ViewModels
🧰 Step 2: Create a Kotlin Helper for iOS
This helper exposes functions (not inject() properties) to avoid leaking Lazy<T> into Swift.
// iosMain or commonMain
package your.package.di
import org.koin.core.component.KoinComponent
import org.koin.core.parameter.parametersOf
import org.koin.core.component.get
import org.koin.core.component.inject
object DependencyHelper : KoinComponent {
// Zero-parameter
val someViewModel by inject<SomeViewModel>()
// Parameterized
fun someViewModelWithId(id: Long): SomeViewModelWithId =
get { parametersOf(id) }
}
🧱 Step 3: Usage in SwiftUI
You create a thin Swift view model wrapper so SwiftUI can observe state changes, while the internal logic stays in the shared Kotlin ViewModel.
@MainActor
final class SomeViewModelWrapper: ObservableObject {
private let viewModel: SomeViewModelWithId
init(id: Int64) {
viewModel = DependencyHelper.someViewModelWithId(id: id)
}
// The rest of the functions, like observing the state, etc
}
Then, use it in SwiftUI, like you would with any other Swift view model.
struct DetailsView: View {
@StateObject private var viewModel: SomeViewModelWrapper
init(id: Int64) {
_viewModel = StateObject(wrappedValue: SomeViewModelWrapper(id: id))
}
var body: some View {
// Use viewModel safely
}
}
That’s it. No more crashes. No more passing repositories around. Just clean, type-safe DI that works.
✨ Why This Approach Shines
- ✅ No reified crash: Swift never calls
get<T>(); Kotlin handles it. - ✅ Minimal exposure: Swift only passes runtime args, not DI plumbing.
- ✅ Type-safe & ergonomic: Call sites stay clean and predictable.
- ✅ Scalable: Add helper functions only for the dependencies you actually use on iOS.
🧭 Alternatives You Can Compare
| Approach | Pros | Cons |
|---|---|---|
| Helper functions (this article) | Clean, type-safe, Swift-friendly | Manual helper maintenance |
| Qualifiers / resolveByName | Generic bridge | Strongly typed |
| Factory interfaces | Great for complex params | Slightly more boilerplate |
| Scopes | Scoped lifecycle | More setup |
| ObjCClass / KClass bridges | When available | Not always supported |
⚠️ Pitfalls & Pro Tips
- Don’t expose
inject()properties directly, they returnLazy<T>, not Swift-friendly, take advantage ofKotlindelegates withby inject(). - Make property wrappers
lazyso they can use constructor params. - Ensure the ViewModel lifecycle in Koin (factory vs. single) fits your SwiftUI usage.
- For multiple params, just use
parametersOf(a, b, c)and mirror it in Swift. - Unit test your helper functions directly in shared code.
🧩 Takeaway
This small pattern keeps your dependency graph fully Kotlin-first and your SwiftUI code clean and declarative.
KMP gives us shared power, but clarity is power too.
Keep DI in Kotlin, let Swift focus on building beautiful views.
💻 [Sample gist / helper code link here]