Home > Software design >  Compose mutableState from viewmodel
Compose mutableState from viewmodel

Time:12-27

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.

  • Related