Home > Software engineering >  onValueChange of textfield is not triggered when selecting an option from exposed dropdownMenu compo
onValueChange of textfield is not triggered when selecting an option from exposed dropdownMenu compo

Time:08-24

I am trying to implement an exposed dropdown composable which I can use in multiple parts of my android jetpack compose app. Whenever I select an item from the dropdownMenu the selectedOption is set in the composable and assigned to the textfield value displaying the correct item. However the onValueChange event of the Textfield displaying the result is not fired. This causes the state not being updated in the viewmodel layer of my app. Following my code of my composable.

// ExposedDropdownComposable.kt    
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PlantExposedSelect(
  options: List<String>,
  optionSelected: String,
  label: String,
  onOptionSelected: (String) -> Unit,
  onFocusChange: (FocusState) -> Unit,
) {
  var expanded by remember { mutableStateOf(false) }
  var selectedOption by remember { mutableStateOf(optionSelected) }
  ExposedDropdownMenuBox(
    expanded = expanded,
    onExpandedChange = {
      expanded = !expanded
    }

  ) {
    TextField(
      readOnly = true,
        value = selectedOption,
      onValueChange = onOptionSelected
      label = { Text(label) },
      trailingIcon = {
        ExposedDropdownMenuDefaults.TrailingIcon(
          expanded = expanded
        )
      },
      colors = ExposedDropdownMenuDefaults.textFieldColors(),
      modifier = Modifier
        .fillMaxWidth()
        .onFocusChanged {
          onFocusChange(it)
        },
    )
    ExposedDropdownMenu(
      expanded = expanded,
      onDismissRequest = {
        expanded = false
      }
    ) {
      options.forEach { selectOption ->
        DropdownMenuItem(
          onClick = {
            selectedOption = selectOption
            expanded = false
            Log.e("selectEdoption", selectedOption)
          }
        ) {
          Text(text = selectOption)
        }
      }
    }
  }
}

This is my code where I use the composable in my AddPlantsScreen

PlantExposedSelect(
  options = options,
  optionSelected = lightState.text,
  label = lightState.hint,
  onOptionSelected = {
    Log.e("eventValue", it)
    viewModel.onEvent(AddEditPlantEvent.EnteredLight(it))
  },
  onFocusChange = {
    viewModel.onEvent(AddEditPlantEvent.ChangedLightFocus(it))
  },
)

How do I make the onClick event of the dropdownItem, trigger the onValueChange event of the Textfield dislaying the selectedOption.

CodePudding user response:

You can simply call the onOptionSelected in the onClick parameter of the DropdownMenuItem instead of using the onValueChange.

Something like:

@Composable
fun PlantExposedSelect(
  //...
  onOptionSelected: (String) -> Unit,
) {
  var expanded by remember { mutableStateOf(false) }
  var selectedOption by remember { mutableStateOf(optionSelected) }

  ExposedDropdownMenuBox(
    //....
  ) {
    TextField(
      value = selectedOption,
      onValueChange = {},   //remove the function
      //...
    )
    ExposedDropdownMenu(
      /** ... **/
      }
    ) {
      options.forEach { selectOption ->
        DropdownMenuItem(
          onClick = {
            selectedOption = selectOption
            expanded = false
            onOptionSelected         //update your viewmodel here
          }
        ) {
          Text(text = selectOption)
        }
      }
    }
  }
}

CodePudding user response:

You can hoist the selected option state like this,

val (optionSelected, setOptionSelected)= remember {
    mutableStateOf("")
} 

PlantExposedSelect(
    options = options,
    optionSelected = optionSelected,
    label = "Label",
    onOptionSelected = {
        Log.e("eventValue", it)
        setOptionSelected(it)
        // viewModel.onEvent(AddEditPlantEvent.EnteredLight(it))
    },
    onFocusChange = {
        // viewModel.onEvent(AddEditPlantEvent.ChangedLightFocus(it))
    },
)

And replace selectedOption in PlantExposedSelect with optionSelected.


Complete code for reference

@Composable
fun PlantExposedSelectSample() {
    val options = listOf("Option 1", "Option 2", "Option 3", "Option 4")
    val (optionSelected, setOptionSelected)= remember {
        mutableStateOf("")
    }
    PlantExposedSelect(
        options = options,
        optionSelected = optionSelected,
        label = "Label",
        onOptionSelected = {
            Log.e("eventValue", it)
            setOptionSelected(it)
            // viewModel.onEvent(AddEditPlantEvent.EnteredLight(it))
        },
        onFocusChange = {
            // viewModel.onEvent(AddEditPlantEvent.ChangedLightFocus(it))
        },
    )
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PlantExposedSelect(
    options: List<String>,
    optionSelected: String,
    label: String,
    onOptionSelected: (String) -> Unit,
    onFocusChange: (FocusState) -> Unit,
) {
    var expanded by remember { mutableStateOf(false) }
    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = {
            expanded = !expanded
        }

    ) {
        TextField(
            readOnly = true,
            value = optionSelected,
            onValueChange = onOptionSelected,
            label = {
                Text(label)
            },
            trailingIcon = {
                ExposedDropdownMenuDefaults.TrailingIcon(
                    expanded = expanded
                )
            },
            colors = ExposedDropdownMenuDefaults.textFieldColors(),
            modifier = Modifier
                .fillMaxWidth()
                .onFocusChanged {
                    onFocusChange(it)
                },
        )
        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = {
                expanded = false
            }
        ) {
            options.forEach { selectOption ->
                DropdownMenuItem(
                    onClick = {
                        onOptionSelected(selectOption)
                        expanded = false
                        Log.e("selectEdoption", selectOption)
                    }
                ) {
                    Text(text = selectOption)
                }
            }
        }
    }
}
  • Related