KOIN - Dependency Injection for Kotlin and KMM

KOIN - Dependency Injection for Kotlin and KMM

KOIN is the new dependency injection library developed by jetbrains for KMM and kotlin which is much simpler and easier.

KOIN is specifically designed for Kotlin applications and provides a lightweight and pragmatic way to handle dependency injection. It allows you to define and manage the dependencies within your Kotlin application without the need for complex configuration files or boilerplate code.

Behind the scenes, KOIN works by dynamically creating and managing instances of objects (dependencies) based on the configuration provided in your application modules.

Here's a basic overview of how KOIN works:

Module Configuration: In KOIN, you start by defining modules that specify how different parts of your application are constructed and wired together. Modules are typically defined using a DSL (Domain-Specific Language) provided by KOIN.

Component Injection: You can then inject these components or dependencies into your classes or functions using KOIN's injection mechanisms. KOIN provides annotations and functions to facilitate this process.

Scoped Dependencies: KOIN also supports scoping of dependencies, allowing you to create singletons, request-scoped, or other types of scoped components as needed.

// Define a module
val appModule = module {
    single { MyDatabase() } // Singleton
    single { UserRepository(get()) } // Inject MyDatabase into UserRepository
    factory { UserService(get()) } // Factory creates a new instance each time
}

//Initialize in application class
startKoin {
    modules(appModule)
}

// Inject dependencies in your classes
class MyController {
    private val userRepository: UserRepository by inject()

    fun getUser(): User {
        return userRepository.getUser()
    }
}

In this example, appModule defines the dependencies for a simple application, including a database, a repository, and a service. The inject() function is used to inject the dependencies into the MyController class.

KOIN simplifies the process of managing dependencies in Kotlin applications and is a popular choice among Kotlin developers for its simplicity and ease of use.

Simplified overview of how KOIN works internally:

  1. Module Configuration: When you define modules in KOIN, you specify how to create and provide instances of different classes. These modules are essentially blueprints for how to construct objects.

  2. Container: KOIN maintains an internal container to keep track of the module definitions and the created instances of objects. This container is responsible for storing and managing the dependencies.

  3. Dependency Resolution: When your application requests a dependency, either through constructor injection or by using the inject() function, KOIN looks up the module definitions to determine how to create that dependency.

  4. Object Creation: If the dependency hasn't been created yet, KOIN uses the module's definition to construct a new instance of the requested object. It resolves any other dependencies that the requested object might have by recursively checking the container.

  5. Singletons and Scopes: KOIN supports different scopes for objects, such as singleton (one instance throughout the application) or factory (a new instance each time it's requested). It manages the lifecycle of these scoped objects accordingly.

  6. Lifecycle Management: KOIN also provides support for Android components like Activities and Fragments. It can automatically handle the creation and disposal of dependencies associated with the lifecycle of these components.

  7. Automatic Wiring: KOIN uses reflection and Kotlin's type inference to automatically wire dependencies. This means you don't need to explicitly specify how to construct every dependency. KOIN can often figure it out based on the class definitions and constructor parameters.

  8. Lazy Initialization: KOIN often uses lazy initialization, meaning it creates dependencies only when they are requested. This can help improve the performance of your application by not creating unnecessary objects upfront.

  9. Exception Handling: KOIN includes error handling mechanisms to report issues with dependency resolution or module configuration, making it easier to debug any problems that may arise during application startup.

In summary, KOIN simplifies the process of managing dependencies in your Kotlin application by providing a lightweight and pragmatic dependency injection framework. It dynamically creates and manages instances of objects based on the module definitions you provide, allowing you to focus on writing the core logic of your application without worrying too much about how dependencies are constructed and wired together.

Overall full implementation of KOIN:

  1. Define Modules: Create a Koin module that defines how your dependencies are constructed. In this example, we'll define modules for a ViewModel, a repository, and a database.
// AppModule.kt
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

val appModule = module {
    single { TaskDatabase.getDatabase(get()) }
    single { TaskRepository(get()) }
    viewModel { TaskViewModel(get()) }
}
  1. Create a Room Database: Create a Room database class for storing tasks.
// TaskDatabase.kt
@Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {
    abstract fun taskDao(): TaskDao

    companion object {
        private const val DATABASE_NAME = "task_database"

        @Volatile
        private var INSTANCE: TaskDatabase? = null

        fun getDatabase(context: Context): TaskDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    TaskDatabase::class.java,
                    DATABASE_NAME
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}
  1. Create a Repository: Create a repository class that interacts with the database.
// TaskRepository.kt
class TaskRepository(private val database: TaskDatabase) {
    suspend fun getAllTasks(): List<Task> {
        return database.taskDao().getAllTasks()
    }

    suspend fun insertTask(task: Task) {
        database.taskDao().insertTask(task)
    }
}
  1. Create a ViewModel: Create a ViewModel class that uses the repository to provide data to the UI.
// TaskViewModel.kt
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
    val tasks: LiveData<List<Task>> = repository.getAllTasks().asLiveData()

    fun addTask(task: Task) {
        viewModelScope.launch {
            repository.insertTask(task)
        }
    }
}
  1. Inject Dependencies in Activities/Fragments: In your Android activities or fragments, you can inject dependencies using Koin's by viewModel() delegate for ViewModels and by inject() for other dependencies.
class TaskListFragment : Fragment(R.layout.fragment_task_list) {
    private val viewModel: TaskViewModel by viewModel()

    // ...
}
  1. Initialize Koin: In your Application class or an appropriate entry point of your app, initialize Koin with your modules.
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@MyApplication)
            modules(appModule)
        }
    }
}

With this setup, Koin will manage the creation and injection of dependencies, and you can easily use these dependencies in your Android components, such as activities or fragments. This example demonstrates how Koin can simplify dependency management in a real-world Android application.