I have a mutableStateListOf(ReturnItemExamples) and each of the objects in the list are being displayed in a LazyColumn.
The thing I am having trouble with is once I update the quantity using the OutlinedTextField, and it calls the API (on the viewModel) to update the record, after the API returns successful results and updates those to the mutableStateListOf(ReturnItemExamples) list, the update to the quantity, isn't being shown in the OutlinedTextField. I believe that the update is happening and that it is triggering a redraw by the composable, because I have other fields that are being updated, the only field I have trouble is the OutlinedTextField. That is the only one that isn't updating on screen.
Here is the data model that has the quantity property:
class ReturnItemExample (
val itemCode: String? = null,
val itemDescription: String? = null,
var quantity: Int = 0
)
Here's the viewModel:
@HiltViewModel
class ReturnsViewModelExample @Inject constructor(
val app: Application,
val authToken: String,
private val repository: ReturnsRepository,
) : AndroidViewModel(app) {
val returnItems = mutableStateListOf<ReturnItemExample>()
private fun updateReturnExample(quantity: Int, index: Int) {
viewModelScope.launch {
// Call to an API here returns the updated ReturnItemExample object and I update it here
// with a function that sets the item from the API response to the item in the array at
// the specified index
returnItems[index] = API.response
}
}
}
Here is the listview:
@Composable
fun ReturnsListViewExample(
viewModel: ReturnsViewModelExample,
) {
val itemIndex = remember { mutableIntStateOf(0) }
Column {
Box() {
LazyColumn(state = rememberLazyListState()) {
itemsIndexed(viewModel.returnItems) { index, _ ->
ReturnItemViewExample(
viewModel = viewModel,
itemIndex = index
)
}
}
}
}
}
Here is the composable that show the OutlinedTextField I am having trouble with:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ReturnItemViewExample(
viewModel: ReturnsViewModelExample,
itemIndex: Int
) {
val keyboardController = LocalSoftwareKeyboardController.current
val itemData = viewModel.returnItems[itemIndex]
var testDisplayQuantity by remember { mutableStateOf(itemData.quantity.toString()) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Test Quantity",
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center
)
OutlinedTextField(
value = testDisplayQuantity,
onValueChange = { testDisplayQuantity = it },
textStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
maxLines = 1,
modifier = Modifier
.fillMaxWidth(.5f)
.padding(horizontal = 8.dp)
.onKeyEvent {
if (it.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_ENTER) {
if (validQuantity(testDisplayQuantity.toInt())) {
viewModel.updateReturnExample(testDisplayQuantity.toInt(), itemIndex)
}
}
true
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
if (validQuantity(testDisplayQuantity.toInt())) {
viewModel.updateReturnExample(testDisplayQuantity.toInt(), itemIndex)
}
}),
shape = RoundedCornerShape(10.dp)
)
}
}
Have a look at the following line in your code:
It creates a new state variable of type String and initializes it to
itemData.quantity.toString(). After this initialization, there is no more connection to the original itemData object. It is completely independent.I recommend two steps to resolve this problem:
itemData, it will have no effect. Jetpack Compose cannot detect when you modify one property of a state object. Instead, it can only recompose once a reference changed.testDisplayQuantitystate variable or you can introduce logic that refreshes thetestDisplayQuantityvariable whenever theitemDatachanges.Step 1)
In order to update the quantity of an item, create a ViewModel function similar to this:
We replace an instance in the list on position index with the
setfunction and assign a new instance with updated quantity using thecopyfunction. Jetpack Compose detects the change and will recompose the LazyList.Then call it in
onValueChangelike this:Step 2)
Replace all occurences of
testDisplayQuantitywithitemData.quantity.toString()and delete the variable.Alternatively, you can use a
LaunchedEffectto update yourtestDisplayQuantityvariable whenever theitemDatachanges:As a side note, please consider to pass the item itself to the
ReturnItemViewExampleComposable instead of the index in the list. It results in cleaner code and makes your localitemDatastate variable redundant.Then update your Composable signature as follows:
Also, you should update your
ReturnItemExampleclass as follows:When you have a var inside of a data class, this will usually result in things not working in Jetpack Compose.