Search code examples
androidkotlinandroid-jetpack-composeandroid-roomdagger-hilt

Dagger Hilt Missing Binding Error in Kotlin


I am creating a habit-tracking app, when I added room database to my app and also viewmodel and dependency injection by using hilt, I got this error message.

/home/shahzaman/AndroidStudioProjects/HabitsLog3/app/build/generated/hilt/component_sources/debug/com/shahzaman/habitslog/HabitApp_HiltComponents.java:128: error: [Dagger/MissingBinding] android.content.Context cannot be provided without an @Provides-annotated method.
  public abstract static class SingletonC implements HabitApp_GeneratedInjector,
                         ^
  
  Missing binding usage:
      android.content.Context is injected at
          com.shahzaman.habitslog.habitFeature.di.AppModule.provideHabitDatabase(context)
      com.shahzaman.habitslog.habitFeature.data.database.HabitDatabase is injected at
          com.shahzaman.habitslog.habitFeature.presentation.MainActivity.db
      com.shahzaman.habitslog.habitFeature.presentation.MainActivity is injected at
          com.shahzaman.habitslog.habitFeature.presentation.MainActivity_GeneratedInjector.injectMainActivity(com.shahzaman.habitslog.habitFeature.presentation.MainActivity) [com.shahzaman.habitslog.HabitApp_HiltComponents.SingletonC → com.shahzaman.habitslog.HabitApp_HiltComponents.ActivityRetainedC → com.shahzaman.habitslog.HabitApp_HiltComponents.ActivityC]
  The following other entry points also depend on it:
      dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.shahzaman.habitslog.HabitApp_HiltComponents.SingletonC → com.shahzaman.habitslog.HabitApp_HiltComponents.ActivityRetainedC → com.shahzaman.habitslog.HabitApp_HiltComponents.ViewModelC]

So here is my code of MainActivity

package com.shahzaman.habitslog.habitFeature.presentation

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.navigation.NavHostController
import androidx.navigation.compose.*
import com.shahzaman.habitslog.habitFeature.data.database.HabitDatabase
import com.shahzaman.habitslog.habitFeature.presentation.components.Header
import com.shahzaman.habitslog.habitFeature.presentation.components.MyNavigationBar
import com.shahzaman.habitslog.habitFeature.presentation.navigation.Screen
import com.shahzaman.habitslog.habitFeature.presentation.navigation.SetupNavGraph
import com.shahzaman.habitslog.habitFeature.presentation.ui.theme.HabitsLogTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@OptIn(ExperimentalMaterial3Api::class)
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    @Inject
    lateinit var db: HabitDatabase

    private val viewModel: HabitViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        lateinit var navController: NavHostController

        super.onCreate(savedInstanceState)
        setContent {
            HabitsLogTheme {
                navController = rememberNavController()
                val state by viewModel.state.collectAsState()
                var selectedItem by remember { mutableStateOf(0) }
                LaunchedEffect(selectedItem) {
                    val selectedScreen = when (selectedItem) {
                        0 -> Screen.Home.route
                        1 -> Screen.Stat.route
                        2 -> Screen.Setting.route
                        else -> Screen.Home.route
                    }
                    navController.navigate(selectedScreen) {
                        navController.popBackStack()
                    }
                }

                Scaffold(
                    modifier = Modifier.fillMaxSize(),
                    topBar = {
                        Header(
                            state = state,
                            onEvent = viewModel::onEvent
                        )
                    },
                    bottomBar = {
                        MyNavigationBar(
                            modifier = Modifier,
                            selectedItem = selectedItem,
                            onNavigationItemClicked = { index ->
                                selectedItem = index
                            }
                        )
                    }
                ) { paddingValues ->
                    SetupNavGraph(
                        navHostController = navController, paddingValues = paddingValues,
                        state = state
                    )
                }

            }
        }
    }

}

Here is my ViewModel

package com.shahzaman.habitslog.habitFeature.presentation


import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shahzaman.habitslog.habitFeature.data.database.HabitDao
import com.shahzaman.habitslog.habitFeature.domain.habit.SortType
import com.shahzaman.habitslog.habitFeature.domain.mapper.Habit
import com.shahzaman.habitslog.habitFeature.domain.mapper.HabitMapper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class HabitViewModel @Inject constructor(
    private val dao: HabitDao
) : ViewModel() {

    private val _sortType = MutableStateFlow(SortType.TIME)
    private val _habits = _sortType
        .flatMapLatest { sortType ->
            when (sortType) {
                SortType.TITLE -> dao.getHabitsByTitle()
                SortType.TIME -> dao.getHabitsByTime()
            }
        }
        .map { habitEntities ->
            habitEntities.map { HabitMapper.fromEntity(it) }
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

    private val _state = MutableStateFlow(HabitState())
    val state = combine(_state, _sortType, _habits) { state, sortType, habits ->
        state.copy(
            habits = habits,
            sortType = sortType
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), HabitState())

    fun onEvent(event: HabitEvent) {
        when (event) {
            HabitEvent.CheckHabit -> {
                viewModelScope.launch {
                    dao.isHabitCheckedOnDate(
                        state.value.title,
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("EEEE, d MMMM"))
                    )
                }
            }

            is HabitEvent.DeleteHabit -> {
                viewModelScope.launch {
                    dao.deleteHabit(event.habit)
                }
            }

            HabitEvent.HideDialog -> {
                _state.update {
                    it.copy(
                        isAddingHabit = true
                    )
                }
            }

            HabitEvent.SaveHabit -> {
                val title = state.value.title
                val isChecked = state.value.isChecked
                val date = state.value.date
                val time = state.value.time

                if (title.isBlank() || time.isBlank()) {
                    return
                }

                val habit = Habit(
                    title = title,
                    description = title,
                    isChecked = isChecked,
                    date = date,
                    time = time
                )
                viewModelScope.launch {
                    dao.upsertHabit(HabitMapper.toEntity(habit))
                }
                _state.update {
                    it.copy(
                        isAddingHabit = false,
                        title = "",
                        time = ""
                    )
                }
            }

            is HabitEvent.SetTitle -> {
                _state.update {
                    it.copy(
                        title = event.title
                    )
                }
            }

            HabitEvent.ShowDialog -> {
                _state.update {
                    it.copy(
                        isAddingHabit = true
                    )
                }
            }

            is HabitEvent.SortHabit -> {
                _sortType.value = event.sortType
            }
        }
    }
}

here is AppModule file for DI.

package com.shahzaman.habitslog.habitFeature.di

import android.content.Context
import androidx.room.Room
import com.shahzaman.habitslog.habitFeature.data.database.HabitDao
import com.shahzaman.habitslog.habitFeature.data.database.HabitDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {


    @Singleton
    @Provides
    fun provideHabitDatabase(context: Context): HabitDatabase {
        return Room.databaseBuilder(
            context.applicationContext,
            HabitDatabase::class.java,
            "habit_database"
        ).build()
    }

    @Provides
    fun provideHabitDao(habitDatabase: HabitDatabase): HabitDao {
        return habitDatabase.dao
    }
}

here is my build.gradle module file.

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id("com.google.dagger.hilt.android")
}

android {
    namespace 'com.shahzaman.habitslog'
    compileSdk 33

    defaultConfig {
        applicationId "com.shahzaman.habitslog"
        minSdk 26
        //noinspection EditedTargetSdkVersion
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.10.1'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
    implementation 'androidx.activity:activity-compose:1.7.2'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3-android'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'

    implementation("androidx.navigation:navigation-compose:2.6.0")

    implementation("com.google.dagger:hilt-android:2.46.1")
    kapt("com.google.dagger:hilt-android-compiler:2.46.1")

    // Room
    def room_version = "2.5.2"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"


}

kapt {
    correctErrorTypes = true
}

and app level gradle file.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '8.1.0' apply false
    id 'com.android.library' version '8.1.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    id("com.google.dagger.hilt.android") version "2.46.1" apply false
}

How to Solve this?


Solution

  • In the module class, you need to annotate the context with @ApplicationContext as below as documented here.

    @Singleton
    @Provides
    fun provideHabitDatabase(@ApplicationContext context: Context): HabitDatabase {
        return Room.databaseBuilder(
            context.applicationContext,
            HabitDatabase::class.java,
            "habit_database"
        ).build()
    }