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.
Related
Which thread the subsequently called functions are executed?
You may consider a function like the getNewsFeed() function inside the repository class given below -
#Singleton
class NewsFeedRepository #Inject constructor(
private val networkDataSource: NetworkDataSource,
private val diskDataSource: DiskDataSource
) {
#WorkerThread
suspend fun getNewsFeed(): NewsResponse {
return try {
val news = networkDataSource.getNewsFeed()
diskDataSource.updateCache(news)
NewsResponse(news = news)
} catch (ex: Exception) {
NewsResponse(
news = diskDataSource.getNews(),
errorMessage = ex.message
)
}
}
}
I called the getNewsFeed() function from ViewModel using kotlin coroutine as given below -
#HiltViewModel
class MainViewModel #Inject constructor(
private val repository: NewsFeedRepository
) : ViewModel() {
private val _newsResponse = MutableLiveData<NewsResponse>()
val newsResponse: LiveData<NewsResponse>
get() = _newsResponse
init {
viewModelScope.launch {
_newsResponse.value = repository.getNewsFeed()
}
}
}
I tried to use Dispatchers.IO as well but I couldn't update livedata value hence I had to use #WorkerThread.
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
) {
...
}
I am a simple function getCreditReport in viewmodel in which I am trying to make an API call repository layer
I want to test getCreditReport in viewmodel to check that thge repository is called but I get the following error
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.clearscore.data.DataRepository
Mockito cannot mock/spy because :
- final class
What I have tried
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
//private val retroServiceInterface: RetroServiceInterface,
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
init {
creditReportLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = dataRepository.getCreditReport()
creditReportLiveData.postValue(response)
Log.d("data", response.toString())
} catch (e: IOException) {
Log.d("data", e.toString())
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val retroServiceInterface: RetroServiceInterface
) {
suspend fun getCreditReport(): CreditReport {
return retroServiceInterface.getDataFromApi()
}
}
Unit test
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#Mock
private lateinit var dataRepository: DataRepository
#Mock
private lateinit var mainActivityViewModel: MainActivityViewModel
#Test
fun getCreditReport() {
runBlocking {
mainActivityViewModel.getCreditReport()
verify(dataRepository).getCreditReport()
}
}
}
Dependency Injection component - in case this is helpful
#Singleton
#Component(modules = [RetroModule::class])
interface RetroComponent {
fun inject(mainActivityViewModel: MainActivityViewModel)
fun getMainactivityViewModel(): MainActivityViewModel
}
Please suggest what I am doing wrong
Thanks
R
Please try adding this dependency
testImplementation "org.mockito:mockito-inline:3.11.2"
[Adding to help future folks who stumble upon this question]
In my case, the class didn't need to be final so I just removed final from the class declaration.
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
)
}