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
) {
...
}
Related
I am attempting to add Hilt DI to a view model class.
It's not clear to be from the docs (in layman's terms) what the constructor is used for?
All the Google docs also indicate you need to add this : ViewModel interface after the constructor but I am also curious what purpose that interface serves?
#HiltViewModel
class MyViewModel #Inject constructor(
) : ViewModel() {
private val _myVal: MutableState<String> = mutableStateOf("")
val myVal get() = _myVal.value
fun setMyVal (myVal: String) {
_myVal.value = phoneNumber
}
}
In my activity, I have multiple variables being initiated from Intent Extras. As of now I am using ViewModelFactory to pass these variables as arguments to my viewModel.
How do I eliminate the need for ViewModelFacotory with hilt
Here are two variables in my Activity class
class CommentsActivity : AppCompatActivity() {
private lateinit var viewModel: CommentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
val contentId = intent.getStringExtra(CONTENT_ID_FIELD) //nullable strings
val highlightedCommentId = intent.getStringExtra(HIGHLIGHTED_COMMENT_ID_RF) //nullable strings
val commentsViewModelFactory = CommentsViewModelFactory(
contentId,
highlightedCommentId
)
viewModel = ViewModelProvider(this, commentsViewModelFactory[CommentsViewModel::class.java]
}
}
Here is my viewModel
class CommentsViewMode(
contentId : String?,
highlightedCo;mmentId : String?,
) : ViewModel() {
//logic code here
}
My app is already set up to use hilt but in this case How can I pass these 2 variables and eliminate the viewModelFactory entirely
The trick is to initialize those variables only once, while the activity can be created multiple times. In my apps, I use a flag.
View model:
class CommentsViewModel : ViewModel() {
private var initialized = false
private var contentId : String? = null
private var highlightedCommentId : String? = null
fun initialize(contentId : String?, highlightedCommentId : String?) {
if (!initialized) {
initialized = true
this.contentId = contentId
this.highlightedCommentId = highlightedCommentId
}
}
//logic code here
}
Also, you should know that there is an open issue in dagger project exactly for this capability:
https://github.com/google/dagger/issues/2287
You're welcome to follow the progress.
If you want to use hilt effectively u can follow this steps
Use #HiltViewModel in your view model
#HiltViewModel
class MyViewModel #inject constructor(private val yrParameter): ViewModel {}
Also you no longer need any ViewModelFactory! All is done for you! In your activity or fragment, you can now just use KTX viewModels() directly.
private val viewModel: MyViewModel by viewModels()
Or if you want to use base classes for fragment and activity you can use this code to pass viewModel class
abstract class BaseFragment<V: ViewModel, T: ViewDataBinding>(#LayoutRes val layout: Int, viewModelClass: Class<V>) : Fragment() {
private val mViewModel by lazy {
ViewModelProvider(this).get(viewModelClass)
}
}
I'm trying to control ViewModel from a fragment by sending category_id and pass it to the repository which is injected by Hilt.
But ViewModel cant see repository outside of init block. What did I miss?
#HiltViewModel
class ProjectViewModel #Inject constructor(
repository: ScienceTrackerRepository
) : ViewModel() {
private val _flow = MutableStateFlow(LoadUiState.Success(emptyList()))
val flow: StateFlow<LoadUiState> = _flow.asStateFlow()
fun loadProjects(categoryId: Int) {
viewModelScope.launch {
repository.getProjects(categoryId) // unresolved reference "repository"
repository.flowProjects.collect { feed ->
_flow.value = LoadUiState.Success(feed)
}
}
}
init {
viewModelScope.launch {
repository.getProjects(0)
repository.flowProjects.collect { feed ->
_flow.value = LoadUiState.Success(feed)
}
}
}
}
you need to add var or val keyword like this:
#HiltViewModel
class ProjectViewModel #Inject constructor(
val repository: ScienceTrackerRepository
) : ViewModel() {
}
to access the constructor params outside the init block.
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.
I started to implement automated tests recently and I have a little doubt of how to test my ViewModel functions. My project follows some clean architecture concepts, I'm using lib kotlin for mockito (https://github.com/nhaarman/mockito-kotlin) and I would like to do the following test: When call makeLoginUser get success then set loginUserLiveData .
I have studied some concepts and I know the tests I should do, but I am still in doubt as to how to do them, which objects should I mock for a given test and which should be instantiated. I needed a small example to address me.
Here is my test class and some classes of the structure of my project.
TestClass
#RunWith(JUnit4::class)
class MainViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var myRepository: MyRepository
private lateinit var loginUserUseCase: LoginUserUseCase
private lateinit var checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase
private lateinit var logoutUserUseCase: LogoutUserUseCase
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
#Test
fun `When call makeLoginUser get success then set loginUserLiveData`() {
// prepare
myRepository = mock { // should I mock or instantiate?
on { execLoginUser(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } doReturn Flowable.just(true)
}
loginUserUseCase = mock { // should I mock or instantiate?
on { execute(ArgumentMatchers.anyMap()) } doReturn Flowable.just(true)
}
mainViewModel = MainViewModel(loginUserUseCase, checkUserAuthenticatedUsecase, logoutUserUseCase)
val observer = mock<Observer<ResultState<Boolean>>> { mock }
// Execute
mainViewModel.makeLoginUser("test#gmail.com", "123456")
// Check
// ?
}
}
ViewModel
// components 'LoginUserUseCase', 'CheckUserAuthenticatedUsecase' and 'LogoutUserUseCase' injected via koin
class MainViewModel(
private val loginUserUseCase: LoginUserUseCase,
private val checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase,
private val logoutUserUsecase: LogoutUserUseCase
): BaseViewModel() {
val loginUserLiveData = MutableLiveData<ResultState<Boolean>>()
val userAuthenticatedLiveData = MutableLiveData<ResultState<Boolean>>()
val logoutUserLiveData = MutableLiveData<ResultState<Boolean>>()
fun makeLoginUser(email: String, password: String) {
loginUserLiveData.postValue(ResultState.Loading())
loginUserUseCase.execute(mapOf(EMAIL to email, PASSWORD to password))
.subscribe({
loginUserLiveData.postValue(ResultState.Success(it))
}, {
loginUserLiveData.postValue(ResultState.Error(it))
}).addTo(disposables)
}
...
}
UseCase Domain Class
// components 'ThreadExecutor' and 'PostExecutionThread' injected via koin
abstract class FlowableUseCase<T, in Params> constructor(
private val threadExecutor: ThreadExecutor,
private val postExecutionThread: PostExecutionThread) {
protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable<T>
open fun execute(params: Params? = null): Flowable<T> {
return this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.scheduler)
}
}
Abstraction UseCases
// components 'MyRepository', 'ThreadExecutor' and 'PostExecutionThread' injected via koin
// 'MyRepository' abstracts remote and local data repository
class LoginUserUseCase(
private val myRepository: MyRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
): FlowableUseCase<Boolean, Map<String, Any>?>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Map<String, Any>?) = myRepository
.execLoginUser(
params?.get(EMAIL) as String,
params[PASSWORD] as String
)
}