π Stop Wiring Your Android App Manually! Use Hilt + KSP Like a Pro Simplifying Android Dependency Injection (DI) with Hilt & KSP
π§© What Is Dependency Injection, Really?
Create an online Medical Store App MediBihar
π What’s Hilt, and Why Should You Care?
Hilt takes care of:
- Zero boilerplate setup
- Jetpack Compose-friendly
- Built-in support for Android classes
- Compile-time safety
- Google-maintained and production-ready
⚡ Why Replace KAPT with KSP?
KSP > KAPT Because:
- π 2x faster builds (seriously)
- π§ Less IDE lag and random Gradle crashes
- π― Better error messages
- π ️ Built for modern Kotlin projects
More aboutπππ Jetpack Compose Libraries?
How to bootstrap an existing app to use Hilt and KSP?
π± Our Sample App: Dictionary with Jetpack Compose
- π Word search
- π Definitions, examples, parts of speech
- π§ Offline cache using Room
- ⭐ Bookmark favorites
- π£️ Text-to-Speech
- π Dark mode support
- ✅ Dependency Injection via Hilt
- ⚡ Optimized with KSP
π Step-by-Step: Setting Up Hilt with KSP
1️⃣ Add Plugins to build.gradle (Project-level)
id ("com.google.dagger.hilt.android") version "2.56.2" apply false
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
2️⃣ In build.gradle (App-level):
// 1️⃣ Apply Hilt plugin first
id("com.google.dagger.hilt.android")
// 2️⃣ Then apply KSPid("com.google.devtools.ksp")implementation ("androidx.compose.material:material-icons-extended:1.7.8")
// Room Database (Using KSP)
implementation("androidx.room:room-runtime:2.7.1")
implementation("androidx.room:room-ktx:2.7.1")
implementation("androidx.navigation:navigation-compose:2.9.0")
// Hilt Dependency Injection
implementation("com.google.dagger:hilt-android:2.56.2")
ksp("com.google.dagger:hilt-android-compiler:2.56.2")
implementation ("androidx.hilt:hilt-navigation-compose:1.2.0")
// Coil (Image)
implementation("io.coil-kt:coil-compose:2.6.0")
// Retrofit + Gson
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
3️⃣ Create Your Hilt App Class
@HiltAndroidApp
class DictionaryApp : Application()
4️⃣ Annotate Your Entry Points
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DictionaryAppTheme {
/* Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}*/
DictionaryScreen()
}
}
}
}
5️⃣ Inject into ViewModel
@HiltViewModel
class DictionaryViewModel @Inject constructor(
private val repository: DictionaryRepository
) : ViewModel() {
var meanings = mutableStateOf<List<Definition>>(emptyList())
var isLoading = mutableStateOf(false)
var error = mutableStateOf<String?>(null)
fun search(word: String) {
viewModelScope.launch {
isLoading.value = true
error.value = null
try {
meanings.value = repository.getMeaning(word)
} catch (e: Exception) {
error.value = e.localizedMessage ?: "An error occurred"
} finally {
isLoading.value = false
}
}
}
}
6️⃣ Inject into Repository
class DictionaryRepository @Inject constructor(
private val api: DictionaryApi
) {
suspend fun getMeaning(word: String): List<Definition> =
api.getDefinition(word)
}
7️⃣ Provide Dependencies via Module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit =
Retrofit.Builder()
.baseUrl("https://api.dictionaryapi.dev/")
.addConverterFactory(GsonConverterFactory.create())
.build()
@Provides
@Singleton
fun provideDictionaryApi(retrofit: Retrofit): DictionaryApi =
retrofit.create(DictionaryApi::class.java)
}
// === NetworkModule.kt ===
interface DictionaryApi {
@GET("api/v2/entries/en/{word}")
suspend fun getDefinition(@Path("word") word: String): List<Definition>
}
8. DictionaryScreen
package com.codingbihar.dictionaryapp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@Composable
fun DictionaryScreen(viewModel: DictionaryViewModel = hiltViewModel()) {
var word by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp).systemBarsPadding()) {
OutlinedTextField(
value = word,
onValueChange = { word = it },
label = { Text("Enter word") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { viewModel.search(word) }) {
Text("Search")
}
Spacer(modifier = Modifier.height(16.dp))
when {
viewModel.isLoading.value -> CircularProgressIndicator()
viewModel.error.value != null -> Text("Error: ${viewModel.error.value}")
else -> LazyColumn {
items(viewModel.meanings.value) { definition ->
MeaningItem(definition)
}
}
}
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
}
}
@Composable
fun MeaningItem(definition: Definition) {
Column(modifier = Modifier.padding(8.dp)) {
Text("Word: ${definition.word}", style = MaterialTheme.typography.headlineSmall)
definition.meanings.forEach { meaning ->
val color = when (meaning.partOfSpeech.lowercase()) {
"noun" -> Color(0xFFE3F2FD)
"verb" -> Color(0xFFE8F5E9)
"adjective" -> Color(0xFFFFF3E0)
else -> Color(0xFFF3E5F5)
}
Column(
modifier = Modifier
.fillMaxWidth()
.background(color)
.padding(8.dp)
) {
Text("Part of Speech: ${meaning.partOfSpeech}", color = Color.Red ,style = MaterialTheme.typography.titleMedium)
meaning.definitions.forEach { def ->
Text("• ${def.definition}", style = MaterialTheme.typography.bodyMedium)
def.example?.let {
Text(" Example: $it", style = MaterialTheme.typography.bodySmall)
}
}
}
}
}
}





