Search code examples
androidmvvmdagger-2

Dagger MissingBinding error when injecting ViewModel


I have a problem with the dagger injection when I am rebuilding the project. I recently started learning dagger 2 and I encountered this error, can anyone help me? I spent a lot of time on it, your answer will help me a lot.

[Dagger/MissingBinding] com.example.weatherforecast_rxjava_mvvm_dagger2.data.api.interceptors.LoggingInterceptor cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface AppComponent {
                ^
  
  Missing binding usage:
      com.example.weatherforecast_rxjava_mvvm_dagger2.data.api.interceptors.LoggingInterceptor is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppModule.provideLoggingInterceptor(loggingInterceptor)
      okhttp3.logging.HttpLoggingInterceptor is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppModule.provideOkHttp(loggingInterceptor)
      okhttp3.OkHttpClient is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppModule.provideRetrofit(okHttp)
      retrofit2.Retrofit.Builder is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppModule.provideApi(builder)
      com.example.weatherforecast_rxjava_mvvm_dagger2.data.api.WeatherApi is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.data.repository.WeatherRepositoryImpl(weatherApi, �)
      com.example.weatherforecast_rxjava_mvvm_dagger2.data.repository.WeatherRepositoryImpl is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.RepositoryModule.bindRepository(repository)
      com.example.weatherforecast_rxjava_mvvm_dagger2.domain.repository.WeatherRepository is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.ui.WeatherViewModel(repository, �)
      com.example.weatherforecast_rxjava_mvvm_dagger2.ui.WeatherViewModel is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.ViewModelModule.bindViewModel(viewModel)
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.ui.ViewModelFactory(creators)
      com.example.weatherforecast_rxjava_mvvm_dagger2.ui.ViewModelFactory is requested at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppComponent.viewModelFactory()
  The following other entry points also depend on it:
      com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppComponent.inject(com.example.weatherforecast_rxjava_mvvm_dagger2.MainActivity)
[Dagger/MissingBinding] android.app.Application cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface AppComponent {
                ^
  
  Missing binding usage:
      android.app.Application is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.data.location.LocationTrackerImpl(�, application)
      com.example.weatherforecast_rxjava_mvvm_dagger2.data.location.LocationTrackerImpl is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.LocationModule.bindLocationTracker(locationTracker)
      com.example.weatherforecast_rxjava_mvvm_dagger2.domain.location.LocationTracker is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.ui.WeatherViewModel(�, locationTracker)
      com.example.weatherforecast_rxjava_mvvm_dagger2.ui.WeatherViewModel is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.ViewModelModule.bindViewModel(viewModel)
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.example.weatherforecast_rxjava_mvvm_dagger2.ui.ViewModelFactory(creators)
      com.example.weatherforecast_rxjava_mvvm_dagger2.ui.ViewModelFactory is requested at
          com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppComponent.viewModelFactory()
  It is also requested at:
      com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppModule.provideFusedLocationProviderClient(app)
  The following other entry points also depend on it:
      com.example.weatherforecast_rxjava_mvvm_dagger2.di.AppComponent.inject(com.example.weatherforecast_rxjava_mvvm_dagger2.MainActivity)

Below are my classes:

AppComponent

@Singleton
@Component(modules = [AppModule::class, LocationModule::class, RepositoryModule::class, ViewModelModule::class])
interface AppComponent {

    fun inject(mainActivity: MainActivity)

    fun viewModelFactory(): ViewModelFactory
}

AppModule

@Module
object AppModule {

    @Provides
    @Singleton
    fun provideApi(builder: Retrofit.Builder): WeatherApi {
        return builder
            .build()
            .create(WeatherApi::class.java)
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttp: OkHttpClient): Retrofit.Builder {
        return Retrofit.Builder()
            .baseUrl(ApiConstants.API_BASE_URL)
            .client(okHttp)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    }

    @Provides
    @Singleton
    fun provideOkHttp(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .build()
    }


    @Provides
    @Singleton
    fun provideLoggingInterceptor(loggingInterceptor: LoggingInterceptor): HttpLoggingInterceptor {
        val httpLoggingInterceptor = HttpLoggingInterceptor(loggingInterceptor)
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        return httpLoggingInterceptor
    }

    @Provides
    @Singleton
    fun provideFusedLocationProviderClient(app: Application): FusedLocationProviderClient {
        return LocationServices.getFusedLocationProviderClient(app)
    }
}

LocationModule

@Module
interface LocationModule {

    @Binds
    @Singleton
    fun bindLocationTracker(locationTracker: LocationTrackerImpl): LocationTracker
}

RepositoryModule

@Module
interface RepositoryModule {

    @Binds
    @Singleton
    fun bindRepository(repository: WeatherRepositoryImpl): WeatherRepository
}

ViewModelKey

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

ViewModelFactory

@Suppress("UNCHECKED_CAST")
@Singleton
class ViewModelFactory @Inject
constructor(
    private val creators: Map<Class<out ViewModel>,
            @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        var creator: Provider<out ViewModel>? = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }
        if (creator == null) {
            throw IllegalArgumentException("unknown model class " + modelClass)
        }
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

    }
}

WeatherViewModel

class WeatherViewModel @Inject constructor(
    private val repository: WeatherRepository,
    private val locationTracker: LocationTracker
): ViewModel() {

    private val disposables = CompositeDisposable()

    private var _state = MutableStateFlow(WeatherState())
    val state = _state.asStateFlow()

    override fun onCleared() {
        disposables.dispose()
        super.onCleared()
    }

    init {
        locationTracker.getCurrentLocation().subscribe(object : SingleObserver<Location?> {
            override fun onSubscribe(d: Disposable) {
                disposables.add(d)
            }

            override fun onError(e: Throwable) {
                _state.value.weather = State.Error(e.message.orEmpty())
            }

            override fun onSuccess(location: Location) {
                location.let {
                    repository.getWeatherData(it.latitude, it.longitude).subscribe(object : SingleObserver<State<Weather>> {
                        override fun onSubscribe(d: Disposable) {
                            disposables.add(d)
                        }

                        override fun onError(e: Throwable) {
                            _state.value.weather = State.Error(e.message.orEmpty())
                        }

                        override fun onSuccess(weatherState: State<Weather>) {
                            _state.value.weather = weatherState
                        }
                    })
                }
            }
        })
    }
}

WeatherApp

class WeatherApp: Application() {

    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.builder().build()
    }
}

val Context.appComponent: AppComponent
    get() = when (this) {
        is WeatherApp -> appComponent
        else -> this.applicationContext.appComponent
    }

MainActivity

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>

    @Inject
    lateinit var viewModelFactory: ViewModelFactory
    private lateinit var viewModel: WeatherViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appComponent.inject(this)

        permissionLauncher = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) {}
        permissionLauncher.launch(arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
        ))
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel = ViewModelProvider(this, viewModelFactory)[WeatherViewModel::class.java]

        when(val weatherState = viewModel.state.value.weather) {
            is State.Loading -> {
                TODO()
            }
            is State.Success -> {
                TODO()
            }
            is State.Error -> {
                TODO()
            }
        }
    }
}

I have tried to google it but nothing there worked


Solution

  • Your errors say that you are trying to use some objects which you have not provided to a dependency graph.

    1. com.example.weatherforecast_rxjava_mvvm_dagger2.data.api.interceptors.LoggingInterceptor cannot be provided without an @Inject constructor or an @Provides-annotated method.

    In your AppModule you need to provide an instance of LoggingInterceptor to use it for creating HttpLoggingInterceptor. Add this function after provideLoggingInterceptor

    @Provides
    @Singleton
    fun provideLoggingInterceptorImpl(): LoggingInterceptor {
        // return LoggingInterceptor()
    }
    

    Or remove loggingInterceptor parameter from provideLoggingInterceptor function.

    1. android.app.Application cannot be provided without an @Inject constructor or an @Provides-annotated method.

    Looks like your LocationTrackerImpl is injected with an Application. Add these lines into your AppComponent and create it with the Builder, providing Application to the graph:

    @Component.Builder
    interface Builder {
    
        fun build(): AppComponent
    
        @BindsInstance
        fun app(app: Application): Builder
    }
    

    then, where the AppComponent is created:

    val appComponent = DaggerAppComponent.builder().app(this).build()