This is a follow-up to our previous article about Getting Started with KMM, so if you haven’t read that, I would recommend checking it out first to get an idea of how things work in KMM.
If you are here, then I will assume that you already have an idea of dependency injection and the koin. If not, you can check this article to get an idea of how things work with koin in android. Now, let’s get to implementing this into KMM.
Koin provides us with an all-in kotlin library to use it in our shared module to create injections that can be used by both Android and iOS platforms, and also with desktop, if you go with KMP (Kotlin MultiPlatform). But, before we start getting into the coding implementation, there are two things to remember:
1. If you are making an injection of a class that is purely in a shared module and does not have anything to do with the platforms, you should make it in the common_main
. For example, any helper class.
2. If you are making any injection of class that depends on the platform, like ViewModel, etc, you have to create the injections in andorid_main
 & ios_main
 using the expect & actual pattern.
And trust me, it is easier and simpler than you think.
Along with the Koin, we will be using the moko-mvvm library for creating ViewModel, which is also an all-in kotlin library that helps us in creating Viewmodels and Dispatchers that can be used by both android and iOS.
I will break this into three sections, shared , android & iOS .
Let’s add the required dependency first in build.gradle.kts (shared)
.
val commonMain by getting {
   dependencies {
       implementation("io.insert-koin:koin-core:3.3.3")
       api("dev.icerock.moko:mvvm-core:0.13.1")
   }
}
We have added both libraries for koin and moko-mvvm. These are enough to get us going to create ViewModels and injection. But there is another plugin by Koin that helps us make things easier with jetpack compose.
val androidMain by getting {
           dependencies {
               implementation("io.insert-koin:koin-androidx-compose:3.4.2")
           }
       }
This library helps us to get rid of extra code on jetpack compose and use injections in a very simple manner which I will demonstrate in the android section. You have to add this dependency in android level gradle as well.
implementation("io.insert-koin:koin-androidx-compose:3.4.2")
That’s it for libraries. Now let’s get to creating classes that we will be injecting.
class SampleUseCase {
   fun getString(): String = "This is the string coming from usecase in shared module."
}
class SampleViewModel(private val useCase: SampleUseCase) : ViewModel() {
   fun getStringFromUseCase(): String = useCase.getString()
}
Here we have a use-case that will be injected in the ViewModel and the ViewModel that we will be injecting in the android and iOS platforms.
Now, here SampleUseCaseÂ
is entirely for the shared module, so we can create its injection in common_main
, but SampleViewModelÂ
is using ViewModel()
 by moko-mvvm, which on the backend uses expect & actual
 pattern to provide the correct implementation for ViewModel from both android and iOS. As Android has native ViewModels and iOS doesn’t, it provides the correct implementation for it in actualÂ
promise. So, we will need to create ViewModels
injection separately on both android_main
 & ios_main
.
To do this, let’s create expectÂ
in our Platform.kt
 file that will require injection Module from both platforms.
expect fun getPlatform(): Module
Now, let’s create a file named KoinModule.ktÂ
and create a function in it.
import org.koin.core.context.startKoin
import org.koin.dsl.module
fun initKoin() = startKoin {
   modules(
       module {
           single {
               SampleUseCase()
           }
              },
       getPlatform()
   )
}
Here we are initialising Koin and providing it with the required modules using singleÂ
to create a single instance of SampleUseCase and also adding in the platform-specific modules that we will get from android_main & ios_main in the actual of the getPlatform() .
Now, in the Platform.kt
 in android_main
, let’s create an actualÂ
for getPlatform()
.
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
actual fun getPlatform() = module {
   viewModel {
       SampleViewModel(get())
   }}
Here we are utilizing the koin-compose
 plugin to create the injection for ViewModel as native. And get()
 is used to find and inject the required dependency in this ViewModel before making its injection. In this case, it will be singleton of SampleUseCaseÂ
that we have created in common_main
. If you don’t create all of the required dependencies, koin will not be initialized and will crash at build time.
Now, let’s do the same in the Platform.kt
 in ios_main
.
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.dsl.module
actual fun getPlatform() = module {
  single{
      SampleViewModel(get())
  }
}
object GetViewModels: KoinComponent {
   fun getSampleViewModel() = get<SampleViewModel>()
}
Since iOS does not have ViewModels, we are using the singleton pattern. And here we also have created the helper object GetViewModelsÂ
that will provide us with the instance of the required ViewModel. We don’t need it on the android side because of koin-compose
.
That’s it for the shared module.  Let’s move to the platforms.
In Android, first we have to call the initKoin()Â
method from our application to start the koin.
import android.app.Application
import com.example.samplekmmproject.initKoin
class SampleApplication: Application() {
   override fun onCreate() {
       super.onCreate()
       initKoin()
   }
}
And now we can use injections in our composables pretty easily.
@Composable
fun DefaultTextView(viewModel: SampleViewModel = getViewModel()) {
 Text(text = viewModel.getStringFromUseCase())
}
class MainActivity : ComponentActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContent {
     MyApplicationTheme {
       Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
         DefaultTextView()
       }
     }
   }
 }
}
That’s all there is to do. getViewModel()Â
will find and inject the required viewmodelÂ
if there is any, and will throw an exception if you haven’t created the required injection.
On the iOS side, we get to make some tweaking to make things work more smoothly. But don’t worry, these are very simple and easy steps. Let’s get to it.
First of all, you will have to rebuild the project in your Xcode to get all the changes you made in the sharedÂ
module.
Now, first of all, just as we did in the ApplicationÂ
class in android, iOS has the iOSAppÂ
class as its entry point. That is where we will have to start the Koin.
import SwiftUI
import shared
@main
struct iOSApp: App {
  Â
   init() {
       KoinModuleKt.doInitKoin()
   }
  Â
 var body: some Scene {
 WindowGroup {
  ContentView()
 }
 }
}
Don’t worry about the names. Upon compilation from Koltin to Swift, it merges the file extension with its name and changes the helper function names a little bit as well.
Now, in iOS we have ObservableObjectÂ
instead of ViewModels, so we have to convert our ViewModel to make it usable in our UI.
import Foundation
import shared
public class SampleObservableObject: ObservableObject {
   var viewModel: SampleViewModel
  Â
   init(wrapper: SampleViewModel) {
       viewModel = wrapper
   }
  Â
}
public extension SampleViewModel {
   func asObservableObject() -> SampleObservableObject {
       return SampleObservableObject(wrapper: self)
   }
}
Here we have created an ObservableObjectÂ
and an extension function of SampleViewModelÂ
that will return as SampleObservableObject
.
Now in our view.
import SwiftUI
import shared
struct ContentView: View {
   @ObservedObject var viewModel = GetViewModels().getSampleViewModel().asObservableObject()
  Â
 var body: some View {
       Text(viewModel.viewModel.getStringFromUseCase())
 }
}
Here we are using the GetViewModels()
 object we created in ios_main
 to get the singleton instance of SampleViewModelÂ
and then also to convert it into an ObservedObjectÂ
using the extension function we created.
And that is it for both sides. Now run the applications on both of the platforms and you will get the same string we created in SampleUseCase
, like this:
And that's it for Dependency Injection in KMM. Easy, right?