Koin on modularization: How to use it?

Koin on modularization: How to use it?

posted Originally published at dev.to 3 min read

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 viewModel.

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

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"

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"

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)
        }
    }
}

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)
        }
    }
}

class SharedPrefModule {
    companion object {
        fun providePreference(context: Context) : SharedPreferences {
            return context.getSharedPreferences("SharedPreferenceName", Context.MODE_PRIVATE)
        }
    }
}

class RoomModule {
    companion object {
        fun provideRoom(context: Context) : DBConfig {
            return Room.databaseBuilder(context, DBConfig::class.java, "db_sample")
                .fallbackToDestructiveMigration()
                .build()
        }
    }
}

class RepositoryModule {
    companion object {
        val repositoryModule = module(override = true) {
            single<CharacterRepository> { return@single CharacterRepositoryImpl(get(), get()) }
        }
    }
}

class UseCaseModule {
    companion object {
        val useCaseModule = module(override = true) {
            single<CharacterUseCase> { return@single CharacterUseCaseImpl(get()) }
        }
    }
}

class ViewModelModule {
    companion object {
        val viewModelModule = module(override = true) {
            viewModel { GamesListVM(get(), get()) }
            viewModel { GamesDetailVM(get(), get()) }
            viewModel { GamesFavoriteVM(get(), get(), get()) }
        }
    }
}

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()) }
}

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
            )
        }
    }
}

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

Happy Coding :)

Sample project: Github link

If you read this far, tweet to the author to show them you care. Tweet a Thanks
mikkel awesome breakdown..  Quick question—how does Koin compare to Hilt for large apps?
Greate Article man
I would say it depends on your architecture you'd build yet, since Koin doesn't require compile-time check and annotation, it would be slower in the runtime if you don't put the right architecture than hilt which provides trade off compile-time to ensure its correctness.

But yes, if you set right architecture, you can feel koin or hilt performa correctly

More Posts

Koin vs Hilt

samseptiano - Mar 16

How to Fix the TypeError: cannot use a string pattern on a bytes-like object Error in Python

Cornel Chirchir - Oct 29, 2023

Pandas v3.x Defaults Copy-on-Write Feature - Get Used to it Early

Sachin Pal - Jan 7

How to use Builder design Pattern for test data generation in automation testing

Faisal khatri - Oct 14, 2024

Discover how to use AI writing tools without losing your authentic voice as a content creator.

Jimmy McBride - Oct 11, 2024
chevron_left