← Back to list

Setting Up Koin Across Modules in Android (With Build Logic)

Using Koin in a single-module app is straightforward.

Nisarbahoo Hypersoft · 2026-06-17 09:33 · 0 claps · 2.7 min read
#mvi-architecture #android-app-development #android-architecture #koin #build-logic
Open on Medium ↗
Wiki topics: 🏛️ · Architecture

Setting Up Koin Across Modules in Android (With Build Logic)

Using Koin in a single-module app is straightforward.

You define a module, register dependencies, and everything works.

But once your app becomes modular, things change.

Now you have:

  • Feature modules
  • Core modules
  • Shared dependencies
  • Build logic managing configuration

At this point, the real question becomes:

How do you structure DI so it scales with your modules instead of fighting them?

The Core Problem in Modular DI

A common early mistake is this:

Everything is defined in the app module.

startKoin {
    modules(
        dataModule,
        homeModule,
        playerModule,
        // many more...
    )
}

At first, it looks fine.

But as modules grow:

  • The app module becomes a dumping ground
  • Dependencies lose clear ownership
  • Debugging becomes harder
  • Changes affect unrelated modules

This breaks the purpose of modularization.

The Goal

A scalable DI setup should:

  • Let each module define its own dependencies
  • Keep modules independent
  • Avoid duplication
  • Keep the app module as a composition root

Module Owns Its Dependencies

Each module should declare its own DI.

Example: Data Module

val dataModule = module {
    single { VideoApi(get()) }
    single { VideoDao(get()) }
    single<VideoRepository> {
        VideoRepositoryImpl(get(), get())
    }
}

Example: Feature Module

val homeModule = module {
    viewModel { HomeViewModel(get()) }
}

Each module knows only what it needs.

App Module as Composition Root

The app module should only assemble modules.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@MyApp)
            modules(
                dataModule,
                homeModule,
                playerModule
            )
        }
    }
}

No business logic here. Only wiring.

Using Build Logic for Consistency

Instead of adding Koin dependency in every module:

Centralize it.

Version Catalog

[libraries]
koin = "io.insert-koin:koin-android:3.5.3"

Convention Plugin

class KoinConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) = with(target) {
        dependencies {
            add("implementation", libs.findLibrary("koin").get())
        }
    }
}

Apply in Modules

plugins {
    id("your.koin.plugin")
}

Now all modules have consistent DI setup.

Dependency Direction Rules

Even with DI, boundaries must be respected.

feature → core ✅
core → feature ❌
feature → feature ❌ (avoid)

Koin won’t stop you from breaking this , but your architecture will suffer if you do.

Grouping Modules (Scaling Strategy)

As modules increase, group them logically:

val coreModules = listOf(dataModule, commonModule)
val featureModules = listOf(homeModule, playerModule)

Then:

startKoin {
    modules(coreModules + featureModules)
}

Cleaner and easier to scale.

Dynamic Module Loading (Advanced)

For large apps or feature-based delivery:

loadKoinModules(homeModule)

Useful for:

  • On-demand features
  • Reducing startup work

But adds complexity — use only when needed.

Common Mistakes

Putting all DI in app module Breaks modular ownership.

Duplicating dependencies Leads to inconsistency.

Ignoring module boundaries Creates tight coupling again.

Over-engineering early Simple setups are often enough.

Practical Setup by Scale

Small modular app:

  • Basic module DI
  • Single Koin start

Medium app:

  • Module-level DI
  • Build logic for consistency

Large app:

  • Grouped modules
  • Dynamic loading
  • Strict boundaries

Real Insight

Koin itself is simple.

The complexity comes from how you structure your modules.

If each module owns its dependencies and the app just connects them, things stay manageable.

If not, DI becomes another layer of chaos.

Final Thoughts

Dependency injection should support your architecture not fight it.

In modular apps, the key idea is simple:

Each module defines its own world. The app brings those worlds together.

Do this right, and scaling becomes much easier.

What Comes Next

At this point, you’ve covered:

  • Architecture
  • Data layer
  • Async handling
  • Dependency injection
  • Modularization
  • Build logic
  • Multi-module DI

From here, the next level topics are:

  • Offline-first architecture
  • System design for large apps
  • Advanced scaling strategies

This is where Android development starts looking more like system design than just app development.


메타데이터
post_id
fcd46c070b6d
slug
setting-up-koin-across-modules-in-android-with-build-logic-fcd46c070b6d
url
https://medium.com/@nisarbahoo.hypersoft/setting-up-koin-across-modules-in-android-with-build-logic-fcd46c070b6d
canonical_url
https://medium.com/@nisarbahoo.hypersoft/setting-up-koin-across-modules-in-android-with-build-logic-fcd46c070b6d
author_url
https://medium.com/@nisarbahoo.hypersoft
status
ok
fetched_at
2026-06-18 00:10:23