KMP: Injecting Swift classes via Koin

Pedro Alvarez
CodandoTV
Published in
4 min readJan 24, 2025
Image from https://wallpapersok.com/wallpapers/apple-google-chrome-icon-qcmlblwreiaqg0pn.html

In a KMP project, sometimes we handle some aspects in very different ways depending on the platform we are implementing. It becomes even more specific if we are dealing with code written in another language, and that's indeed what happens when we are implementing Dependency Injection for the iOS target with Swift classes, which is in fact something we may have to face in KMP apps. This article will illustrate step by step the process of installing Koin and injecting Swift native classes to be used in the Kotlin iosMain target in a KMP project. I hope you enjoy!

Project Creation and Koin set up

First things first, let's create the KMP project by using the KMP Wizzard. Fill with your project information and select to share UI between iOS and Android platforms. Now we need to install the Koin dependencies in order to our project to work.

Go to the libs.toml file and add the Koin dependencies that shall be installed:

[versions]
koin = "3.5.3"

[libraries]
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin"}
koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin"}

We will rely on koin-core as a general dependency for both platforms and koin-android for Android specific features.

Now sync, add the following libs to our build.gradle.kts file and sync again:

androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
// Add here
implementation(libs.koin.android)
}

commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
// And here
implementation(libs.koin.core)
}

Create a Kotlin interface for the injected class

Now we are gonna create a Kotlin interface that shall be implemented by our injected Swift class, but a very important detail to pay attention is that the iOSApp can only access files from the composeApp (Kotlin target) that exist in the iosMain , otherwise it will not recognize it.

As we also desire to implement our class in the Android target, create an expect interface and specify(with the same body) in each platform.

In the commonMain module:

expect interface ILogger {
fun log(s: String)
}

In the androidMain module:

actual interface ILogger {
actual fun log(s: String)
}

class AndroidLogger: ILogger {
override fun log(s: String) = println("Android Logging $s")
}

In the iosMain module:

actual interface ILogger {
actual fun log(s: String)
}

We are gonna implement it in Swift, but first, build the project to have our ILogger interface accessible by the iosApp.

Create the Swift class

Now open the project on Xcode and add a new Swift class. If you import ComposeApp you will be able to make your class implement the ILogger interface(now a Swift protocol).

import ComposeApp

final class IosLogger: ILogger {
func log(s: String) {
print("Logging \(s)")
}
}

Now that our class is created, we need to be able to inject into our Kotlin application via a Koin module, and this module should be initialized within our platform specific target.

Create a KoinHelper class

Now we are gonna implement a class that will initialize all of our Koin modules with the respective dependencies. Inside iosMain , create the following class:

import org.koin.core.context.startKoin
import org.koin.dsl.module

class KoinInitHelper() {
fun initKoin(logger: ILogger) {
startKoin {
modules(module { single<ILogger> { logger } })
}
}
}

Notice this code starts the Koin modules and receives an ILogger as a parameter, that will be injected via Koin as ILogger . To make it work, we are gonna call this class in our iOS initialization block.

Initialize Koin in iOS

Back to the iOS app, call the helper class in the starter code of our iOS target:

import SwiftUI
import ComposeApp

@main
struct iOSApp: App {

init() {
// Initialize iOS Koin dependencies
KoinInitHelper().doInitKoin(logger: IosLogger())
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Now we are initializing Koin dependencies each time we start the main iOS scene.

In the Android target, create the same class, but that will be started in the MainActivity itself.

import org.koin.core.context.startKoin
import org.koin.dsl.module

class KoinInitHelper {
fun initKoin(logger: ILogger) {
startKoin {
module {
single<ILogger> { logger }
}
}
}
}
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
KoinInitHelper().initKoin(AndroidLogger())
setContent {
App()
}
}
}

Call the Logger classes in each target

Now let's call our logger dependency in the main scene of our app to check everything is working as expected. Create a new Composable screen:

import androidx.compose.runtime.Composable
import org.koin.mp.KoinPlatform

@Composable
fun LoggerScreen() {
// Fetch the Koin dependency
val logger = KoinPlatform.getKoin().get<ILogger>()
logger.log("Pedro Alvarez")
}
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
@Preview
fun App() {
MaterialTheme {
LoggerScreen()
}
}

This is just a blank screen that will call the ILogger dependency and run the specific implementation for the given platform. If we run that on iOS, you will see the message "Logging Pedro Alvarez" will be called twice.

Conclusion

Dealing with specific code may be a tricky task to each hybrid mobile development platform, and that becomes even more complex if dealing with different programming languages. In this article we saw how to create a Swift specific class for iOS, make it conform to a Kotlin interface(protocol) and then injecting this implementation via Koin to be recognized by the default common interface that works for iOS and Android. I hope you enjoyed!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

CodandoTV
CodandoTV

Published in CodandoTV

Aqui você vai encontrar conteúdos de tecnologia da comunidade do CodandoTV

Pedro Alvarez
Pedro Alvarez

Written by Pedro Alvarez

Mobile Engineer | iOS | Android | KMP | Flutter | WWDC19 scholarship winner | Blockchain enthusiast https://www.linkedin.com/in/pedro-alvarez94/

Responses (2)

Write a response