My ViewModel:
class LoginViewModel #ViewModelInject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val currentResult: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun loginUseCase(username: String, password: String) {
viewModelScope.launch {
loginUseCase.invoke(username, password).apiKey.let {
currentResult.value = it
}
}
}
}
Is being used by my MainActivity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val loginViewModel: LoginViewModel by viewModels()
And I know that the ViewModelProvider is expecting a empty constructor but I need to use the LoginUseCase:
class LoginUseCase #Inject constructor(
private val apiService: ApiServiceImpl
) : UseCase<Unit>() {
suspend operator fun invoke(username: String, password: String) =
apiService.login(username, password)
}
Inside the modelView, but i get the error:
Cannot create an instance of class com.example.myboards.ui.login.LoginViewModel
in runtime, and I dont know how I could manage the LoginUseCase inside the LoginViewModel
Provide a ViewModel by annotating it with #HiltViewModel and using the #Inject annotation in the ViewModel object's constructor.
#HiltViewModel
class LoginViewModel #Inject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
...
}
Hilt needs to know how to provide instances of ApiServiceImpl, too. Read here to know how to inject interface instances with #Binds.
Let me know If you still have a problem.
Related
I'm trying to send three values to my UserViewModel but even if sending the savedStateHandle
In my Activity I have
private val viewModel: UserViewModel by viewModels()
Then my UserViewModel is :
#HiltViewModel
internal class UserViewModel #Inject constructor(
private val myRepo: MyRepo,
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
But then this savedStateHandle is empty, what I'm missing?
If you are using MVVM based on the Android Architecture guidelines you can send an event to the viewmodel from your Activity/Fragment once your view is created.
You must add savedStateHandle in AppModule. You want inject savedStateHandle.
I've been using #AssistedInject to do so as follows :
internal class UserViewModel #AssistedInject constructor(
...
#Assisted val name: String,
) : ViewModel() {
...
}
Then I had to create a Factory
#AssistedFactory
interface UserViewModelAssistedFactory {
fun create(name: String): UserViewModel
}
class Factory(
private val assistedFactory: UserViewModelAssistedFactory,
private val name: String, <-- value you want to pass
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return assistedFactory.create(name) as T
}
}
Then in the Activity/Fragment you have to inject the AssistedFactory as follows
#Inject internal lateinit var assistedFactory: UserViewModel.UserViewModelAssistedFactory
private val userViewModel: UserViewModel by viewModels {
UserViewModel.Factory(assistedFactory, intent.getStringExtra(USER_NAME_ARG).orEmpty())
}
Doing this it should work, but also your solution should work make sure you are sending the intent args correctly because it says is null looks like what you are passing is not correct, savedInstace.keys() should return everything you passed from your previous Activity/Fragment.
I am trying to use Hilt to inject a data structure but the compilor said:
#HiltViewModel annotated class should contain exactly one #Inject annotated constructor.
I do not understand why, maybe I misused some of the code for Hilt.
Here is my vieWModel:
#HiltViewModel
class AccountProfileViewModel #Inject constructor() {
#Inject
lateinit var userProfile: UserProfileMemorySource
The UserProfileMemorySource looks like this:
#Singleton
class UserProfileMemorySource #Inject constructor() : UserProfileInterface{
private var userProfile: UserProfile? = null
override fun getUserProfile(): UserProfile? {
return this.userProfile
}
override fun saveUserProfile(userProfile: UserProfile?) {
this.userProfile = userProfile
}
override fun invalidate() {
userProfile = null
}
}
and the data class use is
data class UserProfile(
val name: UserName? = null,
val email: String = "",
val phone: String = "",
val address: Address? = null,
val url: String = ""
)
I am trying to save data into memory.
Any idea why it's not working?
Thanks
Dependencies should be passed through the constructor
#HiltViewModel
class AccountProfileViewModel #Inject constructor(
private val userProfile: UserProfileMemorySource
) {
...
}
Based on the Hilt tutorial, ViewModels needs to be inject the following way:
#HiltViewModel
class ExampleViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() {
...
}
However, in my case, I want to use an interface:
interface ExampleViewModel()
#HiltViewModel
class ExampleViewModelImp #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
...
}
Then I want to inject it via the interface
#AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
How to make this work?
viewModels requires child of ViewModel class
val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()
Had a similar problem where I wanted to Inject the ViewModel via interface, primarily because to switch it with a fake implementation while testing. We are migrating from Dagger Android to Hilt, and we had UI tests that used fake view models. Adding my findings here so that it could help someone whose facing a similar problem.
Both by viewModels() and ViewModelProviders.of(...) expects a type that extends ViewModel(). So interface won't be possible, but we can still use an abstract class that extends ViewModel()
I don't think there is a way to use #HiltViewModel for this purpose, since there was no way to switch the implementation.
So instead, try to inject the ViewModelFactory in the Fragment. You can switch the factory during testing and thereby switch the ViewModel.
#AndroidEntryPoint
class ListFragment : Fragment() {
#ListFragmentQualifier
#Inject
lateinit var factory: AbstractSavedStateViewModelFactory
private val viewModel: ListViewModel by viewModels(
factoryProducer = { factory }
)
}
abstract class ListViewModel : ViewModel() {
abstract fun load()
abstract val title: LiveData<String>
}
class ListViewModelImpl(
private val savedStateHandle: SavedStateHandle
) : ListViewModel() {
override val title: MutableLiveData<String> = MutableLiveData()
override fun load() {
title.value = "Actual Implementation"
}
}
class ListViewModelFactory(
owner: SavedStateRegistryOwner,
args: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, args) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return ListViewModelImpl(handle) as T
}
}
#Module
#InstallIn(FragmentComponent::class)
object ListDI {
#ListFragmentQualifier
#Provides
fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
return ListViewModelFactory(fragment, fragment.arguments)
}
}
#Qualifier
annotation class ListFragmentQualifier
Here, ListViewModel is the abstract class and ListViewModelImpl is the actual implementation. You can switch the ListDI module while testing using TestInstallIn. For more information on this, and a working project refer to this article
Found a solution using HiltViewModel as a proxy to the actual class I wish to inject. It is simple and works like a charm ;)
Module
#Module
#InstallIn(ViewModelComponent::class)
object MyClassModule{
#Provides
fun provideMyClas(): MyClass = MyClassImp()
}
class MyClassImp : MyClass {
// your magic goes here
}
Fragment
#HiltViewModel
class Proxy #Inject constructor(val ref: MyClass) : ViewModel()
#AndroidEntryPoint
class MyFragment : Fragment() {
private val myClass by lazy {
val viewModel by viewModels<Proxy>()
viewModel.ref
}
}
Now you got myClass of the type MyClass interface bounded to viewModels<Proxy>() lifeCycle
It's so simple to inject an interface, you pass an interface but the injection injects an Impl.
#InstallIn(ViewModelComponent::class)
#Module
class DIModule {
#Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()
}
i am try to inject a module to MyViewModel
here is my Module
#Module
#InstallIn(ViewModelComponent::class)
object EngineModule {
#Provides
fun getEngine(): String = "F35 Engine"
}
and this my viewModel
#HiltViewModel
class MyViewModel #Inject constructor(): ViewModel() {
#Inject lateinit var getEngine: String
fun getEngineNameFromViewModel(): String = getEngineName()
}
and it throws
kotlin.UninitializedPropertyAccessException: lateinit property getEngine
has not been initialized
however if i change ViewModelComponent::class to ActivityComponent::class and inject like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject
lateinit var getEngine: String
it works perfectly
any idea how to inject viewModels?
Also you can just remove #Inject constructor since you are already providing the dependency using dagger module:
#HiltViewModel
class MyViewModel (private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
So, Basically you can either provide the dependency using dagger module or constructor injection.
Since required dependency is going to be injected in the ViewModel's constructor, you just need to modify your code in the following way to make it work:
#HiltViewModel
class MyViewModel #Inject constructor(private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
I have a viewmodel that only emits the value of repo when I subscribe to it in the activity. I am trying to unit test the viewmodel (see code below) but I am getting NPE because repo is null. How can I unit test it? Is it possible?
class MainViewModel #ViewModelInject constructor(mainRepository: MainRepository) : ViewModel() {
val repo: LiveData<Resource<List<Repository>>> = mainRepository.getRepositories()
}
#RunWith(JUnit4::class)
class MainViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val mainRepository = mock(MainRepository::class.java)
private lateinit var mainViewModel: MainViewModel
#Before
fun init() {
mainViewModel = MainViewModel(mainRepository)
}
#Test
fun testGetRepos() {
mainViewModel.repo.observeForever(mock()) /* NPE at this point as repo is null*/
verify(mainRepository).getRepositories()
}
}
Create an interface something like IMainRepository have your actual repository class implement it
class MainRepository : IMainRepository
Then change your ViewModel constructor to accept the interface
class MainViewModel #ViewModelInject constructor(mainRepository: IMainRepository) : ViewModel()
Then create a Mock class that implements the interface and what it is suppose to do
class MockMainRepository : IMainRepository
in your test create a new instance of the mock class and pass that to your ViewModel to test
private val mockMainRepository = MockMainRepository()
#Before
fun init() {
mainViewModel = MainViewModel(mainRepository)
}