Dependency Injection with Koin in Compose Multiplatform - by Nimesh Vasani. 🧑🏻💻
- nimesh Vasani
- Aug 1, 2024
- 3 min read
Updated: Aug 3, 2024
In modern software development, managing dependencies effectively is crucial for creating scalable and maintainable applications. Dependency Injection (DI) is a design pattern that helps achieve this by promoting loose coupling and enhancing code modularity. In this post, we'll explore what dependency injection is and how to implement it using Koin in a Compose Multiplatform project.
What is Dependency Injection?

Dependency Injection is a design pattern that involves injecting dependencies (such as services, repositories, or any other objects) into a class, rather than having the class create its own dependencies. This approach offers several benefits:
Loose Coupling: Classes are not tightly bound to specific implementations, making it easier to swap out components.
Testability: It becomes easier to test classes in isolation by providing mock dependencies.
Scalability: Managing dependencies in large projects becomes more straightforward.
Introduction to Koin
Koin is a lightweight dependency injection framework for Kotlin developers. It is designed to be simple, practical, and efficient. Koin integrates seamlessly with Kotlin Multiplatform projects, making it an excellent choice for Compose Multiplatform applications.

Setting Up Koin in Compose Multiplatform
To get started with Koin in a Compose Multiplatform project, follow these steps:
1. Add Koin Dependencies
First, you need to add Koin dependencies to your build.gradle.kts files.
For the common module, add the following dependencies:
On libs.versions.toml:
koin = "3.6.0-alpha3"
koinCompose = "3.6.0-alpha3"
koinComposeMultiplatform = "1.2.0-alpha3"
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref ="koinCompose"}
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koinComposeMultiplatform" }
On shared/build.gradle.kts, dependencies are configured for both commonMain and androidMain:
commonMain.dependencies { ....
api(libs.koin.core)
implementation(libs.koin.compose)
}
androidMain.dependencies {
...
implementation(libs.koin.androidx.compose)
}
2. Define Your Modules
Create a Kotlin file to define your Koin modules. A module in Koin is a container that holds definitions of how to create and inject dependencies.
val appModule = module {
single { Firebase.auth }
single { AuthRepository(Firebase.auth) }
single { FireStoreRepository(Firebase.firestore,Firebase.auth)}
}
Here, we have defined a module appModule that provides a Repository instance as a singleton.
// Two ways to define viewModel Modules(this is more efficient)
//common main side
expect val viewModelModule: Module
//android main
actual val viewModelModule = module { viewModelOf(::MainViewModel) viewModelOf(::FirebaseAuthViewModel) viewModelOf(::FireStoreViewModel)
}
//iOS main
actual val viewModelModule = module {
singleOf(::MainViewModel)
singleOf(::FirebaseAuthViewModel)
singleOf(::FireStoreViewModel)
}
3.Start koin by inserting into Application level
Koin Application is used for start a new koin application in compose context.
@Composable
@Preview
fun App() {
val scope = rememberCoroutineScope()
KoinApplication(application = {
modules(listOf(appModule,viewModelModule))
}
) {
MaterialTheme {
HomeScreen()
}
}
Kotlin serialization consists of a compiler plugin, that generates visitor code for serializable classes, runtime library with core serialization API and support libraries with various serialization formats.
Supports Kotlin classes marked as @Serializable and standard collections.
Data class
package models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("user")
data class User(
val name :String,
val image:String,
val groupsId:List<String> = emptyList(),
val friendsId :List<String> = emptyList()
)
Repository class
class FireStoreRepository(
private val fireStore: FirebaseFirestore, firebaseAuth:FirebaseAuth
) {
suspend fun saveUser() {
if (firebaseUser != null) {
fireStore.collection("user").document(firebaseUser.uid).set(
User.serializer(),
User(
name=firebaseUser.displayName
?:firebaseUser.email!!.dropLast(10),
image = "",
groupsId = listOf(),
friendsId = listOf()),
)
}
}
}
ViewModel for UI state management
class FireStoreViewModel(
private val fireStoreRepository: FireStoreRepository
) : ViewModel() {
private var friends = MutableStateFlow<List<User>>(emptyList()) val friends : StateFlow<List<User>> = friends.asStateFlow()
suspend fun fetchFriends() {
viewModelScope.launch {
val friendData = fireStoreRepository.fetchFriends() _friends.value = friendData
}
}
}
Main Compose class
@Composable
fun MainScreen(
fireStoreViewModel: FireStoreViewModel = koinInject()
) {
val navController = rememberNavController()
LaunchedEffect(Unit) {
// firestoreViewModel.saveUSer()
fireStoreViewModel.fetchFriends()
}
val friends = viewModel.friends.collectAsState()
// UI and use of frinds data from viewModel
}
Don’t forget to add Internet permission on Android Manifest file
androidMain (AndroidManifest.xml)
Conclusion
Dependency Injection simplifies the management of dependencies in your Compose Multiplatform projects, promoting better code organization and testability. Koin is a powerful and easy-to-use DI framework that integrates seamlessly with Kotlin Multiplatform, making it an excellent choice for modern application development. By following the steps outlined in this post, you can effectively implement DI in your Compose Multiplatform projects and enjoy the benefits it brings.
Concluded by Nimesh Vasani.
Comments