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.
Related
How to start executing a block of code after changing the value of MutableLiveData when using .observeAsState()?
Example: MutableLiveData changes and after need to call Toast.
#Composable
fun TextInfo() {
val isSuccess by remember { viewModel.isSuccess.observeAsState() }//var isSuccess = MutableLiveData<Boolean>() — in ViewModel
LaunchedEffect(isSuccess) {
Log.d("IS SUCCESS", "trues")
}
}
Showing a Toast is a side effect, so you need to put it inside a LaunchedEffect. Make the LiveData state the key of the LaunchedEffect. This causes the side effect to only occur when this particular LiveData's value changes.
val myDataState = remember { someLiveData.observeAsState() }
LaunchedEffect(myDataState) {
// show the toast
}
Read about it in the documentation here.
I am migrating from LiveData to Coroutine Flows specifically StateFlow and SharedFlow. Unfortunately emitting values should run on a CoroutineScope thus you have this ugly repetitive code viewModelScope.launch when using it inside a ViewModel. Is there an optimal way of emitting values from this?
class MainSharedViewModel : BaseViewModel() {
private val mainActivityState = MutableSharedFlow<MainActivityState>()
fun getMainActivityState(): SharedFlow<MainActivityState> = mainActivityState
fun setTitle(title: String){
viewModelScope.launch {
mainActivityState.emit(ToolbarTitleState(title))
}
}
fun filterData(assetName: String){
viewModelScope.launch {
mainActivityState.emit(AssetFilterState(assetName))
}
}
fun limitData(limit: Int){
viewModelScope.launch {
mainActivityState.emit(AssetLimitState(limit))
}
}
}
Use tryEmit() instead of emit(). tryEmit() is non-suspending. The reason it's "try" is that it won't emit if the flow's buffer is currently full and set to SUSPEND instead of dropping values when full.
Note, you have no buffer currently because you left replay as 0. You should keep a replay of at least 1 so values aren't missed when there is a configuration change on your Activity/Fragment.
Example:
fun setTitle(title: String){
mainActivityState.tryEmit(ToolbarTitleState(title))
}
Alternatively, you can use MutableStateFlow, which always has a replay of 1 and can have its value set by using value =, just like a LiveData.
I have created function as below :
fun getPercentage(id:String): String {
var percentage=""
scope.launch {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percenatge
}
Here, I can not return the updated value using the varibale : percentage.
Logs I am getting is as below :
$$$ value outside >>
$$$ value >> 50
means I can't return the latest value. something going wrong with the flow.
Someone suggested me to use async{} and await(). But I don't know How it will be helpful here?
Please guide. Thanks.
The launch function launches the coroutine in the background, and then continues. Your "outside" code is therefore running before the "inner" coroutine completes.
Use the async function instead to return a Deferred value from the coroutine:
fun getPercentage(id:String): Deferred<String> {
return scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
}
Note of course that its more likely you want to make getPercentage a suspend function, and then call await directly:
suspend fun getPercentage(id:String): String {
val percentageDeferred = scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
val percentage = percentageDeferred.await()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
It's also likely you want to do something else before the await otherwise you're probably just better off making repo.getPercentage a suspend function as well, and calling it directly:
suspend fun getPercentage(id:String): String {
// if repo.getPercentage is a suspend function, this call suspends
// like the await in the previous example
val percentage = repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
See Concurrent using async in the Kotlin docs.
I don't believe you necessarily need to use async in this case, particularly. You only need to be aware that whatever is inside launch { ... } is executed asynchronously. So by the time getPercentage returns, your coroutine may not have even started yet.
Keeping that in mind, I believe you may want to change the way your code works. The only way you can make fun getPercentage(id: String): String work without changing that signature is by replacing scope.launch { ... } with scope.runBlocking { ... }, but you probably don't want to do that, because it would block your thread.
Instead, you might change getPercentage to be a suspend method:
suspend fun getPercentage(id: String): String {
return repo.getPercentage(id)?.get(0)?.percent.toString()
}
However, suspend methods can only be called from inside a coroutine. So you would need to call it like this:
scope.launch {
val percentage = getPercentage("some ID")
// Now you can use `percentage` for whatever you need.
}
repository.callPermissionRemove(permissionID) is a suspending function which requires a viewModelScope to call within a viewModel. After calling the suspending function in the repository, I would like to reset the MutableLiveData back to null.
May I know what's the difference between these two sets of code and which one should I implement?
Code A
fun callPermissionRemove(permissionID: Int) {
viewModelScope.launch {
permissionRemoveAPIResponse.value = repository.callPermissionRemove(permissionID)
}
permissionRemoveAPIResponse.value = null
}
Code B
fun callPermissionRemove(permissionID: Int) {
viewModelScope.launch {
permissionRemoveAPIResponse.value = repository.callPermissionRemove(permissionID)
permissionRemoveAPIResponse.value = null
}
}
In your Code A example, line, setting response value to null will get called before repository.callPermissionRemove() will execute, whereas in the Code B example, lines will execute in the same order as written.
That being said, when resetting MutableLiveData field to null after use, you might want to consider using SingleLiveEvent. Example of it you can find here, and some more explanation of why and how here.
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