The pain of manual wiring, and DI
Remember the end of lesson 5. To load the menu, we wired everything by hand:
// LESSON 5 — the chain, assembled by hand
object Network {
private val retrofit = Retrofit.Builder()...build()
val api: PicaApi = retrofit.create(PicaApi::class.java)
}
val vm = viewModel { MenuViewModel(MenuRepository(Network.api)) }
It works — but look closer. Every screen that needs a MenuViewModel has to know how to build the whole chain: Network.api → MenuRepository → MenuViewModel. Once lessons 8–9 add TokenStorage, AuthInterceptor and OrderRepository, that chain grows and every new screen repeats the same assembly. Change one piece (say baseUrl) and you go hunting across the codebase.
Dependency injection (DI) flips this. A class stops building its own dependencies — it receives them in its constructor (class MenuViewModel(private val repo: MenuRepository) — which we already do). But who creates them? One central "list of recipes" — the DI container.
Koin is a lightweight DI container for Kotlin. Three core recipes:
single { ... }— build one instance and share it everywhere (e.g.Retrofit,PicaApi, repositories).factory { ... }— build a new instance on each request.viewModel { ... }— likefactory, but tied to a screen's lifecycle (an Android ViewModel).get()— "ask Koin for whatever goes here" — Koin finds and builds the dependency itself.
Recipes live in modules (val coreModule = module { ... }), and the app registers them at launch with startKoin { }.
In this lesson object Network from lesson 5 disappears: coreModule replaces it, and screens receive their dependencies through koinViewModel().