How to update LiveData by set a value to variable - android

I have a variable and a LiveData, and I want to update the LiveData when I set a value to the variable.
var trigger = ""
val updateValue: LiveData<Value> = service.getValue(trigger)
// when set the value1 to trigger, the updateValue need to update the value.
var trigger = "value1"
val updateValue: LiveData<Value> = service.getValue("value1")

Use switchmap:
var trigger = MutableLiveData<String>()
val updateValue: LiveData<Value> = Transformations.switchMap(trigger) {
service.getValue(it)
}
Then:
trigger.value = "value1"

Related

Assignment operation from constant value in Kotlin not working

In the ViewModel of my Android app built with Kotlin, The method shown below sets the state to the values passed in to it. But if the calling code passes in a String constant, it is not set to the constant's value. Instead, it is set to an empty string. In the example below, passing in a ZERO_STRING constant actually assigns an empty string "". How can I pass in a constant and still make this work?
Constants File
ZERO_STRING = "0"
ITEM_QUANTITY_STRING = "ItemQuantity"
Method in ViewModel
private val _shoppingListItemState = MutableLiveData(
savedStateHandle.get<ShoppingListItem>("shoppinglistitem")
?: ShoppingListItem()
)
override val shoppingListItemState: LiveData<ShoppingListItem>
get() = _shoppingListItemState
override fun setStateValue(stateToEdit: String, stateValue: Any?) {
var itemToUpdate = _shoppingListItemState.value!!
when(stateToEdit) {
ITEM_QUANTITY_STRING -> itemToUpdate = itemToUpdate.copy(quantity = stateValue.toString())
}
_shoppingListItemState.value = itemToUpdate
}
//This works
setStateValue(ITEM_QUANTITY_STRING, "0")
//This does not work. It assigns "" instead of "0"
setStateValue(ITEM_QUANTITY_STRING, ZERO_STRING)

Recommposition is not happening when i update a MutableStateFlow<List<AttendanceState>>

I have a MutableStateFlow<List<AttendanceState>>,
var attendanceStates = MutableStateFlow<List<AttendanceState>>(arrayListOf())
private set
My AttendanceState data class.
data class AttendanceState (
var memberId: Int,
var memberName: String = "",
var isPresent: Boolean = false,
var leaveApplied: Boolean = false
)
The list is rendered by a LazyColumn
The LazyColumn contains Checkboxes.
If i update the checkbox, the event is propagated to the ViewModel and from there I'm changing the value in the list
attendanceStates.value[event.index].copy(leaveApplied = event.hasApplied)
attendanceStates.value = attendanceStates.value.toList()
But this is not updating the LazyColumn.
Snippet of my implementation:
val attendances by viewModel.attendanceStates.collectAsState()
LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
Log.e("Attendance","Lazy Column Recomposition")
items(attendances.size) { index ->
AttendanceCheckBox(attendanceState = attendances[index], modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), onAttendanceStatusChangeListener = { viewModel.onEvent(AttendanceEvent.IsPresentStatusChanged(index, it)) }, onLeaveAppliedStatusChangeListener = { viewModel.onEvent(AttendanceEvent.IsLeaveAppliedStatusChanged(index, it)) })
}
}
Re-composition is not happening.
Try this:
viewModelScope.launch {
val helper = ArrayList(attendanceStates.value)
helper[event.index] = helper[event.index].copy(leaveApplied = event.hasApplied)
attendanceStates.emit(helper)
}
Changing an item's properties will not trigger a StateFlow, you have to replace the whole item with the changed item and emit a new list.
I would recommend SnapshotStateList instead of a standard List, this will guarantee an update without having to create a new instance of it like what you would do with an ordinary List, assuming you call AttendanceState instance copy() and updating at least one of its properties with a different value.
var attendanceStates = MutableStateFlow<SnapshotStateList>(mutableStateListOf())
private set
I would also recommend changing the way you use your LazyColumn where items are mapped by their keys not just by their index position,
LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
items(attendances, key = {it.memberId}) {
AttendanceCheckBox(...)
}
}
and if you still need the index position.
LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
itemsIndexed(attendances, key = { index, item ->
item.memberId
}) { item, index ->
AttendanceCheckBox(...)
}
}
You should also use update when updating your StateFlow instead of modifying its value directly to make it concurrently safe.
attendanceStates.update { list ->
val idx = event.idx
list[idx] = list[idx].copy(leaveApplied = event.hasApplied)
list
}

mutableStateListOf change not reflecting in UI - Jetpack Compose

in my ViewModel:
private val _itemList = mutableStateListOf<Post>()
val itemList: List<Post> = _itemList
fun likePost(newPost: Post){
val index = _itemList.indexOf(newPost)
_itemList[index] = _itemList[index].copy(isLiked = true)
}
Here my Post data class:
data class Post(
val id: Int,
val name: String,
val isLiked: Boolean = false,
)
And here my Composable:
val postList = viewModel.itemList
LazyRow(content = {
items(postList.size) { i ->
val postItem = postList[i]
PostItem(
name = postItem.name,
isLiked = postItem.isLiked,
likePost = { viewModel.likePost(postItem)}
)
}
})
The change does not update in the UI instantly, I first have to scroll the updated item out of the screen so it recomposes or switch to another Screen and go back to see the change.
For some reason it doesn't like updating, it will add and delete and update instantly. You have to do it this way when updating for our to update the state.
fun likePost(newPost: Post){
val index = _itemList.indexOf(newPost)
_itemList[index] = _itemList[index].copy()
_itemList[index].isLiked = true
}
You are returning a List<> effectively and not MutableStateList from your ViewModel.
If you want the list to not be mutable from the view, I happen to use MutableStateFlow<List<>> and return StateFlow<List<>>. You could also just convert it to a list in your composable.
Edit:
//backing cached list, or could be data source like database
private val deviceList = mutableListOf<Device>()
private val _deviceListState = MutableStateFlow<List<Device>>(emptyList())
val deviceListState: StateFlow<List<BluetoothDevice>> = _deviceListState
//manipulate and publish
fun doSomething() {
_deviceListState.value = deviceList.filter ...
}
In your UI
val deviceListState = viewModel.deviceListState.collectAsState().value

Android Mutablelivedata list item didn't notify changed

I declared variable like this.
var saleData: MutableLiveData<SaleData> = MutableLiveData()
data class
data class SaleData(
var saleType: Int,
var saleDetail: Int,
var salePrice: Int,
var saleRate: Int,
var printAmount: Int
)
and then, init the data
init {
saleData.value = SaleData(saleType = 1, saleDetail = 0, salePrice = 0, saleRate = 0, printAmount = 1)
}
The question is, if one of the components of the data class in SaleData changes, can I be notified of this?
I simply wrote the code as below, but there was no result value.
viewModel
fun changeData() {
saleData.value?.saleRate = 50
}
fragment - at onCreateView
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.saleData.observe(viewLifecycleOwner, Observer { saleData ->
Log.d(TAG,"value changed")
})
I can't get the Log when change the saleRate in saleData
Like this code,
saleData.value = SaleData(saleType = saleType, saleDetail = 0, salePrice = 0, saleRate = 0, printAmount = 1)
I set saleData value, it notify the value changed but I want when change the item of saleData, notify the change
Is there anything else I need to set up?
A call to saleData.value?.saleRate = 50 will not notify observers because the underlying object stored within the MutableLiveData remains unchaged. For LiveData to notify its observers, you need to assign a new object to it.
Assuming SaleData is a data class, you can make it notify it's observers by calling,
saleData.value = saleData.value?.copy(saleRate = 50)
This will notify all registered observers.
As Rafsanjani said, you need to assign an updated reference of the SaleData class to the LiveData. Once you do that, the LiveData observer will notify the changes. For better understanding, please, see the code below.
SaleDataViewModel
class SaleDataViewModel: ViewModel() {
private var _saleData: MutableLiveData<SaleData> = MutableLiveData()
val saleData: LiveData<SaleData> = _saleData
init {
_saleData.value = SaleData(saleType = 1, saleDetail = 0, salePrice = 0, saleRate = 0, printAmount = 1)
}
fun updateSale() {
val saleRate = updatedSaleRate()
_saleData.value = SaleData( _saleData.value!!.saleType, _saleData.value!!.saleDetail, _saleData.value!!.salePrice, saleRate, _saleData.value!!.printAmount)
}
private fun updatedSaleRate(): Int {
return 50
}
}
SaleDataFragment
class SaleDataFragment: Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) {
viewModel.updateSale()
viewModel.saleData.observe(viewLifecycleOwner, Observer {
Log.d(TAG, "Notify change")
})
}
}

Operations With Android LiveData in EditText?

I have 3 editext and I have to multiply the numbers written in the first two, and make the result appear live on the third using Android Livedata.
viewModel.num1.observe(this,
Observer { newNum1-> binding.ediText1.text = newNum1.toString() }
viewModel.num2.observe(this,
Observer { newNum2-> binding.ediText2.text = newNum2.toString() }
viewModel.num3.observe(this,
Observer { newNum3-> binding.ediText3.text = newNum3.toString() }
I tried something like this, with 2 MutableLiveData and one MediatorLivedata, but i did something wrong because it didn't update live the third EditText. Could someone help me?
class MyViewModel : ViewModel() {
private var num1 = MutableLiveData<Int>();
private var num2 = MutableLiveData<Double>();
private var mun3 = MediatorLiveData<Double>();
num3.addSource(num1, this::onChanged);
num3.addSource(num2, this::onChanged);
private fun onChanged(x : Double) {
var a = num1.value
var b = num2.value
if (a== null)
a= 0;
if (b== null)
b= 0.0;
num3.setValue(a * b);
}
}
I'm using Kotlin but i accept any kind of code, even in java.
Thank you for your patience and help!
Best regards, Mark.
Please try something like that, but be aware of nullability. One of received values can be null
fun <A, B> LiveData<A>.combineWith(b: LiveData<B>): LiveData<Pair<A?, B?>> =
MediatorLiveData<Pair<A?, B?>>().apply {
var lastA: A? = this#combineWith.value
var lastB: B? = b.value
addSource(this#combineWith) {
lastA = it
value = Pair(lastA, lastB)
}
addSource(b) {
lastB = it
value = Pair(lastA, lastB)
}
}
viewModel.num1
.combineWith(viewModel.num2)
.observe(
this,
Observer { (first, second) ->
if (first != null && second != null) {
someEditText.text = (first * second).toString()
}
}
)
That might not be your literal code, because it's not compileable. You can't pass the same function to be used as the observer for either source, since the source types are different.
Your onChanged() function doesn't use the input parameter, so you can remove that and call it from a lambda you pass to each addSource call.
You can also simplify the content of your function by using Elvis operators.
private val num1 = MutableLiveData<Int>()
private val num2 = MutableLiveData<Double>()
private val num3 = MediatorLiveData<Double>().apply {
addSource(num1) { onChanged() }
addSource(num2) { onChanged() }
}
private fun onChanged() {
val a = num1.value ?: 0
val b = num2.value ?: 0.0
num3.value = a * b
}

Categories

Resources