With the growing popularity of Kotlin Multiplatform (KMP), various Kotlin-based tools have seen significant evolution, one of which is the Dependency Injection framework “Koin.” Using Koin for DI in Android and iOS within a KMP project presents both advantages and challenges. Despite being the most popular DI framework in KMP, Koin has limitations, particularly when it comes to accessing instances in your iOS Swift module. In this article, I’ll guide you on how to effortlessly “get()” your instances in iOS, just as you would in your Kotlin module.
Setup
I will skip the basic setup part as it is well documented in Koin’s official documentation and there is not much to change.
Problem
In a Kotlin Multiplatform (KMP) project, once you’ve set up your Koin application with defined modules, injecting dependencies into your Kotlin classes is straightforward using the KoinComponent
interface. However, KoinComponent
isn’t available in iOS, which complicates dependency injection. To address this, you’ll need to create a custom “Provider” for your instances.
val viewmodelModule = module {
factory { SplashViewModel(get(), get()) }
//..other dependencies
}
object PresenterProvider: KoinComponent {
fun provideSplashViewModel(): SplashViewModel = get()
//..similar functions for all your dependencies
}
And to use in in iOS
@StateObject var splashViewModel: SplashViewModel = PresenterProvider.shared.provideSplashViewModel()
This approach can feel cumbersome, requiring additional overhead to create a provider. Moreover, the way to access these dependencies in Swift isn’t as clean or straightforward as in Kotlin.
Solution
To address this issue, we will create our own custom get()
method in Swift that replicates the functionality of the Kotlin get()
method, allowing us to retrieve dependencies seamlessly within the iOS environment.
Step 1: Obtain the Koin Application Instance
Koin’s documentation provides a brief guide on initializing your Koin application from iOS. This step is crucial as it sets up the foundation for dependency injection across both your Kotlin and Swift modules.
//This method is called from App class of your ios project
fun initKoin(){
startKoin {
modules(appModule())
}
}
While this approach works, we can unlock even more potential by fully utilizing the startKoin
function. This function returns the Koin application, which allows us to load/unload modules and even retrieve instances with get()
. To leverage this in iOS, we can modify the dependency helper in iosMain
as follows.
class DependenciesProviderHelper {
fun initKoin() {val iosModule = module {
//your ios modules
}
val instance = startKoin {
modules(getBaseModules() + iosModule)
}
koin = instance.koin
}
companion object {
lateinit var koin: Koin
}
}
Step 2: Creating Methods to Retrieve Instances
We need to implement a method in Kotlin that is interoperable with Swift, allowing us to retrieve instances using generics. To achieve this, we will create two methods outside the DependencyProviderHelper
class, located in iosMain
.
@OptIn(BetaInteropApi::class)
fun Koin.get(objCClass: ObjCClass): Any {
val kClazz = getOriginalKotlinClass(objCClass)!!
return get(kClazz, null, null)
}@OptIn(BetaInteropApi::class)
fun Koin.get(objCClass: ObjCClass, qualifier: Qualifier?, parameter: Any): Any {
val kClazz = getOriginalKotlinClass(objCClass)!!
return get(kClazz, qualifier) { parametersOf(parameter) }
}
Now we simply need to call these methods from swift using the koin instance that we already have
func get<A: AnyObject>() -> A {
return DependenciesProviderHelper.companion.koin.get(objCClass: A.self) as! A
}func get<A: AnyObject>(_ type: A.Type) -> A {
return DependenciesProviderHelper.companion.koin.get(objCClass: A.self) as! A
}
func get<A: AnyObject>(_ type: A.Type, qualifier: (any Koin_coreQualifier)? = nil, parameter: Any) -> A {
return DependenciesProviderHelper.companion.koin.get(objCClass: A.self, qualifier: qualifier, parameter: parameter) as! A
}
Step 3: Using these get methods
Once implemented, you can seamlessly use these methods throughout your app. For example:
var splashViewModel: SplashViewModel = get()
That’s all.
If you’re interested in learning how to get()
an observable object and simplify the use of Kotlin ViewModels in Swift, let me know in the comments. I’d be happy to write a follow-up post on that topic.