Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The class cannot be provided without an @Provides-annotated method

I want to inject a dependency (HomeViewModel) into my fragment (HomeFragment).

I have a class (HomeViewModelImpl) which implemented that abstraction (HomeViewModel) and inside this class, I'm overriding parent's methods of course.

The abstraction class (HomeViewModel) is an abstract class which extended from BaseViewModel.

The BaseViewModel is a normal open class in which extended from ViewModel class from the Android lifecycle component.

The problem is I got an error when I want to inject HomeViewModel into the fragment:

> error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] com.example.mvvm.ui.home.HomeViewModel cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.mvvm.MyApplication> {
            ^
  com.example.mvvm.ui.home.HomeViewModel is injected at
      com.example.mvvm.ui.home.HomeFragment.viewModel
  com.example.mvvm.ui.home.HomeFragment is injected at
      dagger.android.AndroidInjector.inject(T)

HomeFragment:

class HomeFragment : BaseFragment() {
//Error comes from this line
@Inject
lateinit var viewModel: HomeViewModel
}

HomeViewModel:

//If I write @Inject annotation here, the error goes away,
//but then I have to remove the abstract keyword, then I have to open the class
//and the useful usage of that abstract class in HomeViewModelImpl class
//will be gone, and I have to set open keyword on the HomeViewModel and
//on its method.
/*open*/ abstract class HomeViewModel /*@Inject constructor()*/ : BaseViewModel() {

sealed class State {
    data class AlbumsLoaded(val albums: List<AlbumData>) : State()
    object ShowLoading : State()
    object ShowContent : State()
    object ShowError : State()
}

abstract fun fetchAlbums()
}

BaseViewModel:

open class BaseViewModel : ViewModel() {

private val compositeDisposable: CompositeDisposable = CompositeDisposable()

protected fun addDisposable(disposable: Disposable) {
    compositeDisposable.add(disposable)
}

private fun clearDisposables() {
    compositeDisposable.clear()
}

override fun onCleared() {
    clearDisposables()
}
}

HomeModule:

@Module(includes = [
//HomeModule.HomeViewModelProvide::class,
HomeModule.HomeVM::class])
internal abstract class HomeModule {

@ContributesAndroidInjector
internal abstract fun homeFragment(): HomeFragment

@Module
abstract class HomeVM {
    @Binds
    @IntoMap
    @ViewModelKey(HomeViewModelImpl::class)
    internal abstract fun bindHomeViewModel(viewModel: HomeViewModelImpl): HomeViewModel
//I've changed the return type of this method from HomeViewModel to
//BaseViewModel and ViewModel, but error still exists!
}

//I've written this to provide HomeViewModel, but compiler shows another error
//that says there is a dependency circle!
/*@Module
class HomeViewModelProvide {
    @Provides
    internal fun provideHomeViewModel(homeViewModel: HomeViewModel): HomeViewModel = homeViewModel
}*/
}

ViewModelKey:

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

ViewModelFactory:

class ViewModelFactory @Inject constructor(
    private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, 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 {
        @Suppress("UNCHECKED_CAST")
        return creator.get() as T
    } catch (e: Exception) {
        throw RuntimeException(e)
    }
}
}

ViewModelModule:

@Module
internal abstract class ViewModelModule {

@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}

BaseModule:

@Module
internal abstract class BaseModule {

@ContributesAndroidInjector(modules = [HomeModule::class])
internal abstract fun mainActivity(): MainActivity
}

AppComponent:

@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
ViewModelModule::class,
AppModule::class,
BaseModule::class
])
interface AppComponent : AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>()
}

Note: Please read inline comments on above snippet codes.

All I want is set HomeViewModel as an abstract class and inject it where I want.

like image 964
Dr.jacky Avatar asked Mar 06 '23 09:03

Dr.jacky


2 Answers

The solution is to create a middle abstract class between the child and the real parent, then the child has to extend from that middle abstract class.

HomeViewModel & the Middle Abstract class:

open class HomeViewModel @Inject constructor() : BaseViewModel() {

    sealed class State {
        data class AlbumsLoaded(val albums: List<AlbumData>) : State()
        object ShowLoading : State()
        object ShowContent : State()
        object ShowError : State()
    }

    abstract class Implementation : HomeViewModel() {
        abstract fun fetchAlbums()
    }
}

HomeViewModelImpl:

class HomeViewModelImpl : HomeViewModel.Implementation() {

    override fun fetchAlbums() { }
}

HomeFragment:

class HomeFragment : BaseFragment() {

    @Inject
    lateinit var viewModel: HomeViewModel
}

Source: https://stackoverflow.com/a/18331547/421467

like image 196
Dr.jacky Avatar answered Mar 10 '23 09:03

Dr.jacky


If you're trying to inject a child of an abstract base class, you need to let dagger know how to create that instance. That's done via a method on a module that returns an instance of the type and has an @Provides annotation. That class will be called every time it needs to create an instance of the class (if you only wish 1 instance of it, you can also annotate it with a scope annotation like @Singleton).

The reason this is necessary is because the class you're trying to make is abstract. It can't be instantiated directly, so Dagger can't do its normal thing of calling the @Inject constructor or default constructor.

like image 20
Gabe Sechan Avatar answered Mar 10 '23 10:03

Gabe Sechan