Compose Multiplatform ViewModel Injection with Koin DI & Ktor
In this article, I’ll guide you on how to inject ViewModel using Dependency Injection with Koin in Compose Multiplatform. Dependency Injection (DI) is a design pattern used to implement IoC (Inversion of Control), allowing us to create more flexible and decoupled code. Koin is a lightweight DI framework for Kotlin, making it an excellent choice for managing dependencies in Compose Multiplatform projects. With Koin, we can easily define and inject ViewModels, which are crucial for managing UI-related data in a lifecycle-conscious way.
Compose Multiplatform enables building UI for multiple platforms using a single codebase, streamlining the development process. By integrating Koin, we can efficiently manage dependencies across different platforms, ensuring our code remains clean and maintainable. In this tutorial, we’ll start by setting up Koin in a Compose Multiplatform project. Then, we’ll define our ViewModel and configure Koin to provide its dependencies. Finally, we’ll demonstrate how to inject the ViewModel into a composable function.
This step-by-step guide will help you understand the benefits of using Koin for Dependency Injection in Compose Multiplatform projects. Whether you’re new to DI or looking to optimize your existing setup, this article provides practical insights and code examples to enhance your development workflow. By the end of this article, you’ll be equipped with the knowledge to implement and utilize Koin effectively in your projects
If you are new to Compose Multiplatform as well as Ktor you can just go through my previous articles so you can get a clear idea about it.
https://medium.com/proandroiddev/compose-multiplatform-networking with-ktor-koin-part-2-ea394158feb9
Lets Start the Quick Implementation
Inside your version catalog file , i am including here only this 2 dependencies for representing viewmodel support using Koin,
koinMultiplatform="1.2.0-Beta4"
koin-compose = {module="io.insert-koin:koin-compose", version.ref = "koinMultiplatform"}
koin-composeVM = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinMultiplatform" }
inside your build.gradle file
commonMain.dependencies {
implementation(libs.koin.compose)
implementation(libs.koin.composeVM)
}
Lets define Koin Modules KoinModules.kt
val providehttpClientModule = module {
single {
HttpClient {
install(ContentNegotiation) {
json(json = Json { ignoreUnknownKeys = true }, contentType = ContentType.Any)
}
}
}
}
val provideapiServiceModule = module{
single { ApiService(get()) }
}
val provideDataSourceModule = module {
single<RemoteDataSource> { RemoteDataSource(get()) }
}
val provideRepositoryModule = module {
single<Repository> { Repository(get()) }
}
val provideviewModelModule = module {
viewModelOf(::HomeViewModel)
}
fun appModule() = listOf(providehttpClientModule,
provideapiServiceModule, provideDataSourceModule, provideRepositoryModule, provideviewModelModule)
After injecting all above instances your viewmodel class will be
class HomeViewModel : BaseViewModel(), KoinComponent {
val repository: Repository by inject()
val _uiStateProductList = MutableStateFlow<UiState<ApiResponse?>>(UiState.Loading)
val uiStateProductList: StateFlow<UiState<ApiResponse?>> = _uiStateProductList
fun getProductList() = CoroutineScope(Dispatchers.IO).launch {
fetchData(_uiStateProductList) { repository.getProducts() }
}
}
open class BaseViewModel: ViewModel() {
suspend fun <T> fetchData(uiStateFlow: MutableStateFlow<UiState<T?>>, apiCall: suspend () -> Flow<UiState<T?>>) {
uiStateFlow.value = UiState.Loading
try {
apiCall().collect {
uiStateFlow.value = it
}
} catch (e: Exception) {
uiStateFlow.value = UiState.Error(e.message?:"")
}
}
}
Finally create KoinContainer using KoinApplication inside your entry point Composable Function & it will be use to start a new Koin application context.
@Composable
fun App() {
KoinApplication(application = {
modules(appModule())
}) {
MaterialTheme {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally)
HomeScreen()
}
}
}
}
you can use the koinViewModel()
function inside your Composable like this.
@Composable
fun HomeScreen(){
val viewModel: HomeViewModel= koinViewModel()
val state = viewModel.uiStateProductList.collectAsState()
LaunchedEffect(Unit) {
viewModel.getProductList()
}
when (state.value) {
is UiState.Error -> {
ProgressLoader(isLoading = false)
}
UiState.Loading -> {
ProgressLoader(isLoading = true)
}
is UiState.Success -> {
ProgressLoader(isLoading = false)
(state.value as UiState.Success<ApiResponse>).data?.let {
ProductCard(it.list)
}
}
}
}

Thats it !! you can find the complete implementation in Github !!
Please let me know your valuable inputs in the comments.
I would appreciate Claps if you find this article useful. Thank you !!
Nimit Raja
LinkedIn