Zar E Ahmer
Zar E Ahmer

Reputation: 34380

ViewModel cannot be provided without an @Inject constructor or an @Provides-annotated

Question EDITED

I am injecting ViewModelProvider.Factory to BaseActivity like below

open class BaseActivity : DaggerAppCompatActivity() {

    @Inject
    lateinit var factories: ViewModelProvider.Factory

    inline fun <reified T : ViewModel> getViewModel(): T {
        return ViewModelProvider(this, factories).get(T::class.java)
    }
}

viewModel only works when we inject then like below.

class MainViewModel @Inject constructor( private val alertStore: AlertStore)
    : BaseViewModel(){

    fun showDialog(){
        viewModelScope.launch {
            delay(4000)

            alertStore.showToast("Alert after 4 seconds.")
        }
    }
}

Why this @Inject constructor is necessary in my current implementation

class MainActivity : BaseActivity() {

  private lateinit var viewModel: MainViewModel

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    viewModel = getViewModel()
    viewModel.showDialog()
}

}

App.kt

class App : DaggerApplication() {

override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
    return DaggerAppComponent.builder().addContext(this).build()
    }
}

AppComponent.kt

@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityBuilder::class,
        ViewModelInjector::class

    ]
)
@Singleton
interface AppComponent : AndroidInjector<App> {

    @Component.Builder
    interface Builder {

        fun addContext(@BindsInstance context: Context): Builder

        fun build(): AppComponent
    }
}

AppModule.kt

@Module
class AppModule {

    @Provides
    fun provideViewModelFactories(viewModels: Map<Class<out ViewModel>,
            @JvmSuppressWildcards Provider<ViewModel>>):
            ViewModelProvider.Factory {
        return object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                val factory = viewModels[modelClass]?.get() ?: error(
                    "No factory provided against ${modelClass.name}"
                )
                @Suppress("UNCHECKED_CAST")
                return factory as T
            }
        }
    }
}

ActivityBuilder.kt

@Module
abstract class ActivityBuilder {

    //@Scope("")
    @ContributesAndroidInjector ///(modules = {MainModelFactory.class})
    public abstract MainActivity bindMainActivity();
}

ViewModelInjector.kt

@Module public abstract class ViewModelInjector {

@Binds
@IntoMap
@ViewModelKey(MainViewModel.class)
public abstract ViewModel providesMainViewModel(MainViewModel model);

}

ViewModelKey.kt

@MapKey
@Retention(AnnotationRetention.SOURCE)
annotation class ViewModelKey(
    val value: KClass<out ViewModel>
)

Why do I have to append @Inject constructor to each ViewModel and kindly explain a little why we need @Binds @IntoMap and with ViewModel

Upvotes: 1

Views: 2869

Answers (1)

Sergei Bubenshchikov
Sergei Bubenshchikov

Reputation: 5371

When you use dagger android you should make your activities and fragments as extensions of DaggerActivity (and DaggerFragment for fragments).

class MainActivity : DaggerActivity() {

    @Inject
    lateinit var viewModel: MainViewModel
}

Next you should prepare infrastructure for injection:

  1. Create injectors for each your activity:

    // All your injectors can be defined in this module
    @Module(includes = [AndroidInjectionModule::class])
    interface AppInjectorModule {
    
        @ContributesAndroidInjector(modules = [MainActivityVmModule::class, /*other dependecies*/])
        fun getMainActivityInjector(): MainActivity
    }
    
  2. Create modules to provide view models (can be multiple for one activity) and factory

    @Module(includes = [VmFactoryModule::class])
    abstract class MainActivityVmModule {
    
        // bind implementation of ViewModel into map for ViewModelFactory
        @Binds
        @IntoMap
        @ClassKey(MainViewModelImpl::class)
        abstract fun bindMainVm(impl: MainViewModelImpl): ViewModel
    
        @Module
        companion object {
            @Provides
            @JvmStatic
            fun getMainVm(activity: MainActivity, factory: ViewModelProvider.Factory): MainViewModel {
                // create MainViewModelImpl in scope of MainActivity and inject dependecies by ViewModelFactory
                return ViewModelProviders.of(activity, factory)[MainViewModelImpl::class.java]
            }
        }
    }
    

    Factory can be provided by different module to avoid duplication

    @Module
    interface VmFactoryModule {
    
        @Binds
        // bind your implementation of factory
        fun bindVmFactory(impl: ViewModelFactory): ViewModelProvider.Factory
    }
    
  3. Add activities injectors to AppComponent graph
    @Component(
        modules = [
            AppInjectorModule::class
        ]
    )
    @Singleton
    interface AppComponent : AndroidInjector<App>
    

Additional info: Dagger & Android

Upvotes: 1

Related Questions