-
Notifications
You must be signed in to change notification settings - Fork 88
Home
// This pattern fails in Kotlin 2.x with Hilt:
internal class CompanyInfoRepositoryImpl @Inject constructor(...)
abstract class DashboardModule {
@Binds
internal abstract fun bindRepository(impl: CompanyInfoRepositoryImpl): CompanyInfoRepository
}Error: 'public' function exposes its 'internal' parameter type
- Implementation classes can be
internal - But
@Bindsmethods MUST bepublic(notinternal) - Hilt's generated code needs public access to binding methods
// Correct pattern:
internal class CompanyInfoRepositoryImpl @Inject constructor(...)
abstract class DashboardModule {
@Binds // Public, not internal!
abstract fun bindRepository(impl: CompanyInfoRepositoryImpl): CompanyInfoRepository
}CRITICAL: If you have a module providing Hilt dependencies (with @Provides or containing types injected elsewhere), it MUST be an Android Library, not a pure Kotlin module.
// ❌ FAILS - Pure Kotlin module with Hilt dependencies
plugins {
id("kotlin") // Wrong for Hilt!
}
// Network module provides ApiService which is @Inject'ed in feature modules
// Hilt's kapt in app module can't see it!Error: ComponentProcessingStep was unable to process '...' because 'prieto.fernando.core.network.ApiService' could not be resolved.
// ✅ WORKS - Android Library module
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
}
android {
namespace = "prieto.fernando.core.network"
buildFeatures {
buildConfig = true // Required if using BuildConfig
}
}Why: Hilt's annotation processor needs all dependency types on the classpath during kapt processing. Pure Kotlin modules' classes aren't available the same way as Android Library modules during the app module's kapt phase.
Transitive dependencies aren't enough for Hilt's classpath aggregation.
// ❌ App only depends on feature-navigation, which depends on other features
dependencies {
implementation(project(":feature-navigation")) // Transitively gets dashboard & launches
}
// Hilt can't discover ViewModels in feature-dashboard or feature-launches!// ✅ Explicit dependencies in app module
dependencies {
implementation(project(":core-network")) // Direct dep on network
implementation(project(":feature-dashboard")) // Explicit!
implementation(project(":feature-launches")) // Explicit!
implementation(project(":feature-navigation"))
implementation(libs.kotlinx.serialization.json) // If used in Hilt modules
}Reason: Hilt's annotation processor needs to see all modules with @InstallIn, @HiltViewModel, etc., directly on the app's classpath during kapt.
w: Support for language version 2.0+ in kapt is in Alpha and must be enabled explicitly. Falling back to 1.9.
[WARN] Issue detected with dagger.internal.codegen.ComponentProcessor
Add to gradle.properties:
# Enable Kotlin 2.0+ support in kapt (Alpha but necessary)
kapt.use.k2=true
kapt.include.compile.classpath=false # Best practice for kapt
# Share test components across modules
dagger.hilt.shareTestComponents=trueNote: K2 kapt is in Alpha, but it's required for Kotlin 2.1.0. Expect some warnings but it works.
Invalid Gradle JDK configuration found.
Inconsistent JVM-target compatibility detected for tasks
Remove hardcoded JDK paths, enable auto-detection:
# gradle.properties
# ❌ Remove hardcoded paths:
# org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
# ✅ Use auto-detection:
org.gradle.java.installations.auto-detect=true
org.gradle.java.installations.auto-download=false
# Remove obsolete JVM args for Java 17+:
# org.gradle.jvmargs=-Xmx4096m -Dkotlin.daemon.jvm.options=--illegal-access=permit
org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8Update gradle-wrapper.properties:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip# gradle/libs.versions.toml
[versions]
kotlin = "2.1.0"
androidGradlePlugin = "8.7.3"
hilt = "2.54"
kotlinxCoroutines = "1.10.1"
kotlinxSerialization = "1.8.0"
compose = "1.7.6"
composeCompiler = "2.1.0" # Must match Kotlin version!
[plugins]
kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }Critical:
- Kotlin 2.x requires
org.jetbrains.kotlin.plugin.composeplugin - Compose Compiler version MUST match Kotlin version
- Remove manual
kotlinCompilerExtensionVersionfrombuild.gradle.kts
android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15" // Manual version
}
}plugins {
alias(libs.plugins.kotlin.compose.compiler) // Plugin handles it!
}
android {
// No kotlinCompilerExtensionVersion needed!
buildFeatures {
compose = true
}
}Build was configured to prefer settings repositories over project repositories
but repository 'Google' was added by build file 'build.gradle.kts'
Move ALL repository declarations to settings.gradle.kts:
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
// ❌ Remove from root build.gradle.kts:
// allprojects { repositories { ... } }When removing dependency management from custom plugins, ensure basic setup remains:
// buildSrc/src/main/kotlin/prieto/fernando/android/plugin/AndroidPlugin.kt
class AndroidPlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
pluginManager.apply("com.android.library")
pluginManager.apply("kotlin-android")
pluginManager.apply("kotlin-kapt")
pluginManager.apply("io.gitlab.arturbosch.detekt")
// Configure Android but DON'T add dependencies here!
// Let each module manage its own dependencies
}
}
}Lesson: Custom plugins for configuration only, not dependency management.
@Module
@InstallIn(ViewModelComponent::class) // ViewModel-scoped dependencies
abstract class FeatureViewModelModule {
@Binds
@ViewModelScoped
abstract fun bindUseCase(impl: UseCaseImpl): UseCase
}
@Module
@InstallIn(SingletonComponent::class) // Singleton dependencies
abstract class FeatureDataModule {
@Binds
@Singleton
abstract fun bindRepository(impl: RepositoryImpl): Repository
}Key: Separate modules for different scopes. ViewModels and their direct dependencies in ViewModelComponent, repositories and network in SingletonComponent.
// shared-testing module (pure Kotlin)
dependencies {
implementation(libs.androidx.arch.core.testing) // ❌ Fails!
}Error: Incompatible because this component declares a component, with the library elements 'aar'
// ✅ Android-specific test deps go in feature modules
// shared-testing should only have pure Kotlin test utilities
dependencies {
implementation(libs.kotlinx.coroutines.test)
implementation(libs.junit)
implementation(libs.mockk)
// No AndroidX testing libraries!
}// In feature-launches module referencing shared-ui resources
ErrorAnimation(R.raw.error_animation) // ❌ Wrong R!import prieto.fernando.shared.ui.R as SharedUiR
ErrorAnimation(SharedUiR.raw.error_animation) // ✅ Correct!Best Practice: Always use type-safe resource imports with aliases in multi-module projects.
When implementing custom Navigation (not Jetpack Nav):
// Key components needed:
@Serializable
sealed interface NavKey {
@Serializable data object Dashboard : NavKey
@Serializable data object Launches : NavKey
}
class NavBackStack(initialBackStack: List<NavKey>) {
var backStack by mutableStateOf(initialBackStack)
// State management + serialization for process death
}
@Composable
fun NavDisplay(backStack: NavBackStack) {
AnimatedContent(targetState = backStack.current) { key ->
resolveNavKeyToContent(key).invoke()
}
}# Nuclear option that actually works:
rm -rf */build .gradle
./gradlew clean
./gradlew assembleDebugReason: Hilt's annotation processor sometimes caches stale metadata. Full clean forces regeneration.
feature-dashboard/
├── domain/
│ ├── model/ # Domain models
│ ├── repository/ # Repository interfaces
│ └── usecase/ # Use cases
├── data/
│ ├── model/ # Data models
│ ├── mapper/ # Data ↔ Domain mappers
│ └── repository/ # Repository implementations (internal)
├── presentation/
│ ├── vm/ # ViewModels + Contract
│ └── ui/ # Screens + UiModels
└── di/ # Hilt modules
Key: Each feature is self-contained with its own data + domain layers. No shared data or domain modules.
| Error | Cause | Solution |
|---|---|---|
Cannot create an instance of ViewModel |
Hilt can't find dependencies | Ensure all modules are Android Libraries + explicit deps |
ComponentProcessingStep was unable to process |
Missing type on classpath | Add explicit dependency in app module |
'public' function exposes 'internal' type |
Kotlin 2.x visibility rules | Make @Binds methods public |
Build Type contains custom BuildConfig fields, but the feature is disabled |
Missing buildConfig flag | Add buildFeatures { buildConfig = true }
|
Support for language version 2.0+ in kapt is in Alpha |
Kotlin 2.x without K2 kapt | Add kapt.use.k2=true to gradle.properties |
- Use
com.android.libraryplugin (notkotlinplugin) - Add
namespace = "your.package.name" - Add
buildFeatures { buildConfig = true, compose = true } - Add explicit dependency in app module's
build.gradle.kts - Keep
@Bindsmethods public (not internal) - Use
@InstallIn(ViewModelComponent::class)for ViewModel deps - Use
@InstallIn(SingletonComponent::class)for singleton deps - Add
.gitignorefor the module - Create
AndroidManifest.xml(can be minimal:<manifest />)
- Update to Gradle 8.14+
- Add
kotlin-compose-compilerplugin - Remove manual
kotlinCompilerExtensionVersion - Add
kapt.use.k2=trueto gradle.properties - Update all Hilt, Compose, Coroutines versions
- Remove hardcoded JDK paths
- Enable JDK auto-detection
- Clean build everything:
rm -rf */build && ./gradlew clean assembleDebug
- Feature-First Architecture: Each feature module is self-contained with domain, data, and presentation layers
- No Shared data/domain Modules: Only shared UI components and test utilities
- Core Modules: Only for truly cross-cutting concerns (network, testing utilities)
- Explicit Over Transitive: Always declare direct dependencies explicitly
- Android Libraries Everywhere: All modules with Hilt dependencies must be Android Libraries
- K2 kapt is in Alpha but stable enough for production
- Build times are acceptable with proper Gradle caching
- Runtime performance is unaffected by these changes
- Hilt annotation processing adds ~5-10s to clean builds
-
Proguard Warnings:
[WARN] Issue detected with dagger.internal.codegen.ComponentProcessor- These are benign, code generation still works - K2 Kapt Alpha Warning: Expected, can be ignored
-
Compose Navigation Deprecations:
rememberSystemUiControlleris deprecated, migrate toEdgeToEdgewhen possible
Last Updated: December 25, 2025 Gradle Version: 8.14.3 Kotlin Version: 2.1.0 AGP Version: 8.7.3 Hilt Version: 2.54