Hilt inject to viewmodel using the same way as to activity - android

I'm wondering, why my injecting works only in activity:
it works:
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject lateinit var fakeRepo: FakeRepo
}
#Module
#InstallIn(ActivityComponent::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
BUT when I'm trying inject this repo to viewModel this way it gives errors:
#Module
#InstallIn(MainActivityViewModel::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
interface FakeRepo {
fun getData(): List<Int>
}
class FakeRepoImpl #Inject constructor(): FakeRepo {
override fun getData(): List<Int> {
return listOf(3,5,6)
}
}
#HiltViewModel
class MainActivityViewModel #Inject constructor(val fakeRepo: FakeRepo) : ViewModel() {
}
Why it doesn't work ? I get
#InstallIn, can only be used with #DefineComponent-annotated classes
Probably I can inject it using provide as singleton but this is not the solution.
Could someone explain why I can inject to activity but to viewmodel not using #Binds ?

Related

Inject an instance of repository class into Application class which is provided by Module with ViewModelScope

I am having a problem that I need inject an instance of repository class into Application class which is provided by Module (Installed in ViewModelComponent, and provide function marked with #ViewModelScope annotation)
Repository
interface IARepository
class ARepository #Inject constructor() : IARepository
Module
#Module
#InstallIn(ViewModelComponent::class)
interface RepositoryModule {
#Binds
#ViewModelScoped
fun provideARepos(impl: ARepository): IARepository
}
ViewModel
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1,
private val useCase2: UseCase2,
) {
...
}
Two UseCase1 and UseCase2 are using IARepository, since if I provides IARepository with ViewModelScope, two instance useCase1 and useCase2 will be using the same instance of repository.
It worked until I inject repository into Application (singleton things)
Application
#HiltAndroidApp
class TestApplication : Application() {
#Inject
lateinit var a: IARepository
}
After that I got error
[Dagger/MissingBinding] IARepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint
Application_HiltComponents.java:129: error: [Dagger/MissingBinding] ...core.domain.IARepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
A binding for ....core.domain.IARepository exists in ...Application_HiltComponents.ViewModelC:
....core.domain.IARepository is injected at
[...Application_HiltComponents.SingletonC] ...Application.a
...Application is injected at
...Application_HiltComponents.SingletonC] ...Application_GeneratedInjector.injectMoonRoverApplication
In application, I tried switch to inject directly implementation class is ARepository, it worked fine.
#HiltAndroidApp
class TestApplication : Application() {
#Inject
lateinit var a: ARepository
}
But I still want to use interface. Are there any solution for it?
I think you have to use #Provides in module as below
#Module
#InstallIn(ViewModelComponent::class)
interface RepositoryModule {
#Binds
#ViewModelScoped
#Provides
fun provideARepos(impl: ARepository): IARepository
}
Also add #HiltViewModel in your view model as below
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1, private val useCase2: UseCase2
) {
...
}
I hope it will help you.
in viewmodel please specify the #HiltViewModel
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1, private val useCase2: UseCase2
) {
...
}
edited:-
#Module
#InstallIn(SingletonComponent::class)
object Module {
#Provides
fun ProvideImplRepo() = ImplRepo()
}
#Module
#InstallIn(ViewModelComponent::class)
abstract class RepositoryModule {
#Binds
abstract fun bindLoginRepository(impl: ImplRepo): Repo
}

Android Hilt cannot be provided without an #Provides-annotated method. in repository Interface Class

I'm getting this error error: [Dagger / MissingBinding] com.eduramza.domain.repositories.RemoteRepository cannot be provided without an # Provides-annotated method. when implementing my repository interface with android hilt.
That's because my useCase implements my repository interface. What may be wrong with my implementation, below is the code:
app.Viewmodel:
#HiltViewModel
class RemoteListViewModel #Inject constructor(
private val useCase: GetTickersUseCase
): ViewModel() {
}
domain.usecase:
class GetTickersUseCase #Inject constructor(
private val remoteRepository: RemoteRepository)
: SingleUseCase<MainCoins> {
override suspend fun executeCall(): Flow<Result<MainCoins>> = remoteRepository.readAllTickers()
}
domain.repository:
interface RemoteRepository {
suspend fun readAllTickers(): Flow<Result<MainCoins>>
}
core.repositoryImpl:
class RemoteRepositoryImpl #Inject constructor(
private val apiService: BraziliexService,
private val tickersMapper: TickersMapper
) : RemoteRepository{
override suspend fun readAllTickers(): Flow<Result<MainCoins>> {
TODO("Not yet implemented")
}
}
core.module:
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
My multimodule app in this structure
where core implement domain, and app implement both.
why is the bind method not being initialized?
You using the ActivityComponent but the RemoteRepository is the indirect dependency of ViewModel so it should be tied with the ViewModel Lifecycle
so instead of ActivityComponent
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
Use this ViewModelComponent
#Module
#InstallIn(ViewModelComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}

Inject Activity into another dependent class

I'v been trying for some time now to get my dagger2 code setup correctly in my project, but I can't seem to figure out how do it correctly. My app roughly looks like this:
class MyApplication : DaggerApplication() {
...
}
class MainActivity : DaggerAppCompatActivity() {
...
}
abstract class BaseFragment() : DaggerFragment() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
...
fun <T : ViewModel> getViewModel(c: Class<T>): T = getViewModel(c, requireActivity())
fun <T : ViewModel> getViewModel(c: Class<T>, owner: ViewModelStoreOwner): T{
return ViewModelProvider(owner, viewModelFactory)[c]
}
}
class AConcreteFragment : BaseFragment() {
private lateinit var viewModel: AViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = getViewModel(AViewModel::class.java)
}
class AViewModel #Inject constructor(private val someApiManager: SomeApiManager ) : ViewModel() {
...
}
class SomeApiManager #Inject constructor(private val activity: MainActivity) {
...
fun authenticate(){
3rdPartyLibrary.openLoginActivity(activity,...)
}
}
Before I introduced SomeApiManager which depends on MainActivity everything worked fine. Then I had dagger2 setup to something like this:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ApplicationModule::class,
FragmentBuilder::class,
ViewModelModule::class,
...
]
)
interface ApplicationComponent: AndroidInjector<MyApplication> {
...
}
#Module
public abstract class FragmentBuilder {
#ContributesAndroidInjector()
abstract fun bindAConcreteFragment(): AConcreteFragment?
...
}
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
...
}
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
internal abstract fun aViewModel(viewModel: AViewModel): ViewModel
...
}
But how should change my dagger2 code to be able to provide MainActivity to SomeApiManager?
As #DavidMedenjak mentioned I did not scope things correctly. My modified dagger2 code now looks like this:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ApplicationModule::class,
ActivityBuilder::class,
...
]
)
interface ApplicationComponent: AndroidInjector<MyApplication> {
...
}
#Module()
public abstract class ActivityBuilder {
#ActivityScope
#ContributesAndroidInjector(modules = [
FragmentBuilder::class,
ViewModelModule::class
])
abstract fun bindMainActivity(): MainActivity?
}
#Module
public abstract class FragmentBuilder {
#FragmentScope
#ContributesAndroidInjector()
abstract fun bindAConcreteFragment(): AConcreteFragment?
...
}
#ActivityScope
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
...
}
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
internal abstract fun aViewModel(viewModel: AViewModel): ViewModel
...
}
Not sure if it it's entirely correct, still not fully grasping everything that's going on. But it does work.

Dagger2 Binding issue

I have two modules one for ViewModelModule Providers and another for Application level which contains retrofit,intent. So before passing to the component I have included the ViewModelModule to the ApplicationModule like this
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
And My component interface is Like:
#Singleton
#Component(modules = [ApplicationModule::class, ContextModule::class])
interface AppComponent {
fun inject(activity: LoginActivity)
fun inject(activity: RegisterActivity)
fun inject(activity: SplashActivity)
}
ApplicationModule class
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
#Singleton
#Named("GotoLogin")
#Provides
fun provideSplashIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, LoginActivity::class.java)
}
#Singleton
#Named("GotoDashboard")
#Provides
fun provideLoginIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, DashboardActivity::class.java)
}
#Singleton
#Named("GotoRegister")
#Provides
fun provideRegisterIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, RegisterActivity::class.java)
}
#Singleton
#Provides
fun provideTimer(): Timer {
return Timer()
}
}
ViewModelModule class
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindviewmodelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(RegisterViewModel::class)
abstract fun bindRegisterViewModel(registerViewModel: RegisterViewModel): ViewModel
}
Scenario:
Suppose when I trying to use a function from ApplicationModule Class in LoginActivity then the Error is coming.
Usage:
class LoginActivity : AppCompatActivity() {
#Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var loginViewModel: LoginViewModel
lateinit var context: Context
#Named("GotoRegister")
#Inject
lateinit var regiseterIntent: Intent
But when I am trying to use anything from the ApplicationModule class a compile error is coming
Crony\app\build\tmp\kapt3\stubs\debug\com\app\crony\di\AppComponent.java:8: error: [Dagger/MissingBinding] androidx.appcompat.app.AppCompatActivity cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent {
^
androidx.appcompat.app.AppCompatActivity is injected at
com.app.crony.di.ApplicationModule.provideRegisterIntent(appCompatActivity)
#javax.inject.Named("GotoRegister") android.content.Intent is injected at
com.app.crony.LoginActivity.regiseterIntent
Full Source Code:
Github Link
I can feel that I am missing something but not able to sort out the issue.
Replace activity with context. It will work well.

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
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:
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
}
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
}
Add activities injectors to AppComponent graph
#Component(
modules = [
AppInjectorModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App>
Additional info: Dagger & Android

Categories

Resources