I have 2 Mutable States in my ViewModel
and want them to observe the textfields in my composable.
ViewModel
:
@HiltViewModel
class AddEditTransactionViewModel @Inject constructor(
private val moneyManagerUseCases: MoneyManagerUseCases
) : ViewModel() {
private val _transactionDescription = mutableStateOf("")
val transactionDescription: State<String> = _transactionDescription
private val _transactionAmount = mutableStateOf("")
val transactionAmount: State<String> = _transactionAmount
Composable
:
@Composable
fun AddEditTransactionScreen(
navController: NavController,
viewModel: AddEditTransactionViewModel = hiltViewModel(),
) {
// This works
var descriptionState by remember { mutableStateOf("") }
var amountState by remember { mutableStateOf("") }
// This doesn't work
var viewModelDescriptionState = viewModel.transactionDescription.value
var viewModelAmountState = viewModel.transactionAmount.value
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = viewModelDescriptionState,
onValueChange = { viewModelDescriptionState = it },
label = { Text(text = "Description") })
Spacer(modifier = Modifier.padding(20.dp))
TextField(
value = viewModelAmountState,
onValueChange = { viewModelAmountState = it },
label = { Text(text = "Amount") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
It works if I use the state from my composable but that kind of beats the purpose of the ViewModel.
CodePudding user response:
The first line:
var descriptionState by remember { mutableStateOf(") }
It works because you're updating the value of mutableState
via delegation. It's easier to understand if you look at how you would do same without delegation:
val descriptionState = remember { mutableStateOf("") }
//...
descriptionState.value = "new value"
remember
will save same object between recompositions, but this object have value
field which is changing.
On the other hand, here:
var viewModelDescriptionState = viewModel.transactionDescription.value
you create a local variable, and any changes will not be saved between recompositions.
This is usually solved by creating a setter function within the view model. You can also use delegation in the view model, if you want to restrict it to update without a setter, you can add private set
, instead of having private _variable
and public variable
:
var transactionDescription by mutableStateOf("")
private set
fun updateTransactionDescription(newValue: String) {
transactionDescription = newValue
}
This is considered best practice because the setter can contain more logic than a simple update value. In the case where you only need to store it, you can remove the private
part and update it directly, without the setter function.
You can find more information about the state in documentation Compose, including this youtube video which explains the basic principles.
CodePudding user response:
Solution
AddEditTransactionScreen
:
@Composable
fun AddEditTransactionScreen(
navController: NavController,
viewModel: AddEditTransactionViewModel = hiltViewModel()
) {
val descriptionState = viewModel.transactionDescription
val amountState = viewModel.transactionAmount
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = descriptionState.value,
onValueChange = { viewModel.updateTransactionDescription(it) },
label = { Text(text = "Description") })
Spacer(modifier = Modifier.padding(20.dp))
TextField(
value = amountState.value,
onValueChange = { viewModel.updateTransactionAmount(it) },
label = { Text(text = "Amount") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
}
AddEditTransactionViewModel
:
@HiltViewModel
class AddEditTransactionViewModel @Inject constructor() : ViewModel() {
private val _transactionDescription = mutableStateOf("")
val transactionDescription: State<String> = _transactionDescription
private val _transactionAmount = mutableStateOf("")
val transactionAmount: State<String> = _transactionAmount
fun updateTransactionDescription(value: String) {
_transactionDescription.value = value
}
fun updateTransactionAmount(value: String) {
_transactionAmount.value = value
}
}
Explanation
Its transactionDescription
and transactionAmount
attributes are of the State<String>
type, the State
type cannot have its value changed, unlike the MutableState
type, a simple solution that maintains the change protection only by the view model, is creating a method for each attribute that you want to change and make this change in the attribute that is MutableState
.