DEV Community

Cover image for Koin on modularization: How to use it?
mikkel septiano
mikkel septiano

Posted on

1 1

Koin on modularization: How to use it?

What is Modularization in Android?

Modularization in Android means breaking an app into smaller, independent modules instead of keeping all code in a single project. Each module has its own responsibility and can be developed, tested, and maintained separately.

Basic Prerequisites to Understand Modularization

Before learning modularization, you should be familiar with:

  • Android Project Structure – Understanding how an app is built using app/src/main.
  • Gradle & Dependencies – Knowing how dependencies are managed in build.gradle.kts.
  • Packages & Code Organization – Basics of organizing files using packages.
  • Types of Android Modules –
  • App Module (Main application)
  • Library Module (Reusability across projects)
  • Feature Module (For dynamic delivery in large apps)

Why Modularization?
✅ Faster Build Times – Only modified modules get compiled.
✅ Better Code Maintainability – Easier to manage and scale.
✅ Reusability – Common features like networking or authentication can be shared across projects.
✅ Parallel Development – Teams can work on different modules independently.

Today we are going to set up step by step how to use Koin in modularization with Room, Retrofit, and viewModel (along with UseCase and Repository).

Before we get started. Create some new modules and structured them like this:
package_level

Step 1
You have to determine what koin gradle version you want to use. For this tutorial, I use 2.2.2 version. Define the gradle into di module

implementation "io.insert-koin:koin-android:2.2.2"
implementation "io.insert-koin:koin-androidx-viewmodel:2.2.2"
implementation "io.insert-koin:koin-androidx-scope:2.2.2"
Enter fullscreen mode Exit fullscreen mode

Step 2
After you have decided koin version you'd use, add other dependency your project would need for injecting to koin in di module as well. In this tutorial I want to inject Retrofit for network, AndroidX Room for local DB, Shared Preferences, ViewModel for managing data, and Coroutine for threading

implementation "io.insert-koin:koin-android:2.2.2"
implementation "io.insert-koin:koin-androidx-viewmodel:2.2.2"
implementation "io.insert-koin:koin-androidx-scope:2.2.2"
implementation "javax.inject:javax.inject:1"

implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"

implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"

implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
Enter fullscreen mode Exit fullscreen mode

Step 3
Define Network Module, Api Module, Shared Preferences Module, Room Module, and ViewModel Module (we also use UseCase and Repository module for clean architecture principle)

class ApiModule {
    companion object {
        val apiModule = module(override = true) {
            single { provideDisneyApi(get()) }
        }

        fun provideDisneyApi(retrofit: Retrofit): Api {
            return retrofit.create(Api::class.java)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class NetworkModule  {
    companion object {
        private fun httpInterceptor() = HttpLoggingInterceptor().apply {
            return HttpLoggingInterceptor { _ ->
            }.apply {
                level = HttpLoggingInterceptor.Level.BODY
            }
        }

        fun provideNetworkHandler(context: Context) = NetworkHandler(context)

        fun provideOkHttpClient() : OkHttpClient {
            return Builder.initInterceptor(httpInterceptor())
        }

        fun provideRetrofitService(okHttpClient: OkHttpClient): Retrofit {
            return Builder.initRetrofit(okHttpClient)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class SharedPrefModule {
    companion object {
        fun providePreference(context: Context) : SharedPreferences {
            return context.getSharedPreferences("SharedPreferenceName", Context.MODE_PRIVATE)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class RoomModule {
    companion object {
        fun provideRoom(context: Context) : DBConfig {
            return Room.databaseBuilder(context, DBConfig::class.java, "db_sample")
                .fallbackToDestructiveMigration()
                .build()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class RepositoryModule {
    companion object {
        val repositoryModule = module(override = true) {
            single<CharacterRepository> { return@single CharacterRepositoryImpl(get(), get()) }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class UseCaseModule {
    companion object {
        val useCaseModule = module(override = true) {
            single<CharacterUseCase> { return@single CharacterUseCaseImpl(get()) }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
class ViewModelModule {
    companion object {
        val viewModelModule = module(override = true) {
            viewModel { GamesListVM(get(), get()) }
            viewModel { GamesDetailVM(get(), get()) }
            viewModel { GamesFavoriteVM(get(), get(), get()) }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

After you add them all, locate and categorize module that you prioritize as a 'core' module and wrap them into AppModule class. In this tutorial I'll wrap retrofit, room, and shared preferences module like this:

val MainAppModule = module(override = true) {
    single { NetworkModule.provideOkHttpClient() }
    single { NetworkModule.provideRetrofitService(get()) }
    single { NetworkModule.provideNetworkHandler(androidContext()) }
    single { RoomModule.provideRoom(androidContext()) }
    single { SharedPrefModule.providePreference(androidContext()) }
}
Enter fullscreen mode Exit fullscreen mode

Step 4
Call them with StartKoin in Application Class in App Module (default module). I will call ViewModel, Repository, Usecase, and Api module as same level with MainApp Module

class DisneyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger(Level.ERROR)
            androidContext(this@DisneyApp)
            modules(MainAppModule, ApiModule.apiModule, RepositoryModule.repositoryModule,
                UseCaseModule.useCaseModule, ViewModelModule.viewModelModule
            )
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

And Voila! Congratulation you've built Koin in modular version. it's easy isn't it?

Happy Coding :)

For sample project, you can refer to this Github

Sentry mobile image

App store rankings love fast apps - mobile vitals can help you get there

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read full post →

Top comments (0)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

👋 Kindness is contagious

DEV is better (more customized, reading settings like dark mode etc) when you're signed in!

Okay