LiveData not updating after changed using Handler PostDelayed? - android

Here's my ViewModel
class MainViewModel(repository: MainActivityRepo) : ViewModel() {
val isLoading: MutableLiveData<Boolean> = MutableLiveData()
init {
isLoading.value = false
android.os.Handler().postDelayed({
isLoading.value = true
Timber.d("isCalled")
}, 5000L)
}
}
I debugged and checked and the log is working perfectly.
The first value of boolean is set correctly, while the second is not

On background thread, you can use post value instead of set value which will solve your problem!

As mentioned by Vikas you should use the postValue() method.
Handler().postDelayed({
isLoading.postValue(true)
Timber.d("isCalled")
}, 5000L)

Related

Viewmodel executes init after updating a MutableStateFlow

whenever I try to update a mutable state flow (uiState), the code in the init of the viewModel executes again (and hence the uiState resets).
This only happens if I use any info related to the previous state of the uiState (at least that's why I think given the following code).
The Code:
init {
Log.d("Error log", "init again")
}
private val _uiState: MutableStateFlow<CreateGameState> =
MutableStateFlow(
CreateGameState.InputDataState(
selectedHeroes = listOf(),
selectedVillain = null,
selectedEncounters = listOf(),
)
)
val uiState: StateFlow<CreateGameState> = _uiState
fun onAction(action: CreateGameActions) {
when (action) {
is CreateGameActions.SelectHero -> {
when (_uiState.value) {
is CreateGameState.InputDataState -> {
_uiState.update {
/* this line causes the error */(it as CreateGameState.InputDataState).copy(selectedHeroes = it.selectedHeroes.plusElement(action.hero))
/* this line doesn't cause error */(it as CreateGameState.InputDataState).copy(selectedHeroes = listOf(Hero.SPIDERMAN))
}
}
}
}
}
}
}
So when only executing the line that causes the error, the Log of the init function is shown in Logcat again.
With this, I loose the uiState and my app gets stuck on the initial state always.
Thanks a lot for your help in advance!
#Tenfour04 was right.
I turns out I was injecting the viewModel in a composable with the constructor as a default parameter. Recomposition was causing the creation of a new ViewModel.
Simply by injecting the same instance of the viewModel solved it.

MutableLiveData wrong value when accessing directly

I have ViewModel:
class SharedViewModel : ViewModel() {
var connected = MutableLiveData(false)
fun setConnected(value: Boolean) {
connected.postValue(value)
}
}
When setting value:
viewModel.setConnected(true)
And then observing it like so:
viewModel.connected.observe(viewLifecycleOwner, {
Log.d(RTAG, "Connected: $it")
})
It works as expected. But sometimes I need acess the value directly and not observing the change like so:
viewModel.setConnected(true)
Log.d(RTAG, "Res: "+viewModel.connected.value) //printed old result, why?
Then I get old result, why is that?
postValue method doesn't change the value immediately. It should be used in a background thread. Try using setValue which should be invoked only on main thread and sets the value immediately.

Initialize property in Kotlin so that the code is executed only once

I would like to know if there is a more idiomatic way of writing the following in Kotlin:
private var loadingFlag: Boolean = false
override fun onResume() {
super.onResume()
if (!loadingFlag) {
// do something
loadingFlag = true
}
}
so far the closest I could come up with is:
val lazyValue: Unit by lazy {
println("computed!")
Unit
}
fun main() {
println(lazyValue)
println(lazyValue)
}
which executes println("computed!") only once as expected.
Basically when I call the code I want println("computed!") to be called only once and subsequent calls do nothing. Any ideas?
I wouldn't consider it idiomatic to have a property with value Unit , or to call a property for its side effect. Those are both code smells.
You can write a custom getter for a Boolean property so it always returns false after the first time it's retrieved. It can be reset by setting it to true.
var firstTime: Boolean = true
get() = field.also { field = false }
This is just convenience. There's nothing non-idiomatic about your first block of code.

Transformed LiveData's value is always null in unit testing even when observed, but works fine in the UI

I want to test a LiveData's value. This LiveData's value depends on another LiveData in the ViewModel. When I run the app, the UI is working as intended. However, when I create a unit test to test the dependent LiveData, the test always fails with an AssertionError saying that the value is always null.
I have the following LiveData in my ViewModel.
class MovieViewModel #ViewModelInject constructor(
private val movieRepository: MovieRepository
) : ViewModel() {
private val state = movieRepository.getMoviesState()
val errorLoading: LiveData<Boolean>
get() = Transformations.map(state) { it == State.ERROR }
val isLoading: LiveData<Boolean>
get() = Transformations.map(state) { it == State.LOADING }
...
}
The LiveData I want to test are errorLoading and isLoading. To do that, I use Mockk to mock objects and have the following code to test them both.
#Test
fun ensure_isLoading_is_true_when_state_is_loading() {
...
every { movieRepository.getMoviesState() } returns MutableLiveData(State.LOADING)
every { booleanObserver.onChanged(any()) } just runs
viewmodel = MovieViewModel(movieRepository)
verify { movieRepository.getMoviesState() }
viewmodel.isLoading.observeForever(booleanObserver)
verify { booleanObserver.onChanged(true) }
assertEquals(true, viewmodel.isLoading.value)
viewmodel.isLoading.removeObserver(booleanObserver)
}
So far, the test can verify that onChanged on the mocked Observer is called and the value of the new change is also correct. However, when I want to access the value of the LiveData I want to test, it always returns null.
I am aware that the LiveData needs to be observed and I have made a mock Observer to do that. However, the test still fails. Why does it always return null?
Simply use this :
#Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new
InstantTaskExecutorRule();
I had the same issue and discovered that the problem is with backing property.
When you use it with get() like this:
val isLoading: LiveData<Boolean>
get() = Transformations.map(state) { it == State.LOADING }
then when calling isLoading you get a new (empty) LiveData instance.
Simply get rid of backing property and use it that way:
val _isLoading = MutableLiveData<Boolean>
val isLoading: LiveData<Boolean> = Transformations.map(state) { it == State.LOADING }

Repository pattern is not correctly returning LiveData

I am using MVVM, LiveData and trying and implement Repository pattern.
But, calling a method in my repository class - RegisterRepo which returns LiveData is not working. I have no idea why. Any suggestions would be greatly appreciated.
Boilerplate code is removed for breivity.
Activity' s onCreateMethod
mViewModel.status.observe(this, Observer {
when (it) {
true -> {
Log.d("----------", " true ") //These message is never being printed.
}
false -> {
Log.d("----------", "false ") //These message is never being printed.
}
}
})
button.setOnClickListener {
mViewModel.a()
}
ViewModel
class AuthViewModel (val repo: RegisterRepo): ParentViewModel() {
//...
var status = MutableLiveData<Boolean>()
fun a() {
status = repo.a()
}
}
RegisterRepo
class RegisterRepo () {
fun a(): MutableLiveData<Boolean> {
var result = MutableLiveData<Boolean>()
result.value = true
return result
}
}
However, if I change my code in ViewModel to this, everything is working fine.
ViewModel
class AuthViewModel (val repo: RegisterRepo): ParentViewModel() {
//...
var status = MutableLiveData<Boolean>()
fun a() {
status.value = true //Change here causing everything work as expected.
}
}
In the first ViewModel code, when method a is called, you assign another LiveData to status variable, this live data is different from the one observed by the Activity, so that the value won't be notify to your Activity
the 2nd way is correct to use and it will work fine the 1st is not working because you are creating new MutableLive data in your RegisterRepo, so basically at the time your create an observable to "status" is deferent where you assign a value into it is different. so the second one is the only way to do this

Categories

Resources