OutlinedTextField in ExposedDropdownMenuBox regains focus

773 views Asked by At

I am currently observing some strange behavior when using an OutlinedTextField inside of an ExposedDropdownMenuBox. If I have already clicked into the OutlinedTextField once, the focus always jumps back to it when I click into another field or sometimes when I'm simply scrolling through my view - preferably after the keyboard is closed manually, if the keyboard keeps visible I can click into the other input fields without any problem.

Has anyone experienced a similar behaviour and knows how to fix this?

I'm using version 1.3.1 of androidx.compose.material:material

My Composable looks like this:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ContributorComposable(contributor: Contributor, logbookEntryState: LogbookEntryState, enabled: Boolean) {
  Spacer(modifier = Modifier.height(8.dp))
  val addContributor: @Composable () -> Unit = {
    IconButton(onClick = { logbookEntryState.contributors.add(Contributor()) }) {
      Icon(imageVector = Icons.Filled.Add, contentDescription = "")
    }
  }
  var expanded by remember { mutableStateOf(false) }

  val options = logbookEntryState.contributorsDropDownList

  Column(modifier = Modifier.fillMaxWidth()) {
    Row(verticalAlignment = CenterVertically, horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
      Spacer(modifier = Modifier.width(40.dp))
      ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = { expanded = !expanded && enabled },
        modifier = Modifier.focusable(false)
      ) {
        Row(verticalAlignment = CenterVertically, horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
          OutlinedTextField(
            colors = if (
              contributor.company.value.isNotBlank() ||
              contributor.registrationNumber.value.isNotBlank() ||
              contributor.userCertificationNumber.value.isNotBlank() ||
              contributor.companyCertificationNumber.value.isNotBlank() ||
              contributor.userFullname.value.isNotBlank()
            ) TextFieldDefaults.outlinedTextFieldColors(unfocusedBorderColor = db_primary_red) else TextFieldDefaults.outlinedTextFieldColors(),

            modifier = Modifier
              .weight(1f),
            value = contributor.userFullname.value,
            onValueChange = { newText ->
              contributor.userFullname.value = newText
              viewModel.findContributorByName(logbookEntryState, newText)
              expanded = true
            },
            label = { Text(stringResource(id = R.string.logbook_entry_create_contributors)) },
            trailingIcon = if (enabled) addContributor else null,
            enabled = enabled,
          )
          if (enabled && logbookEntryState.contributors.size > 1) {
            IconButton(onClick = { logbookEntryState.contributors.remove(contributor) }, modifier = Modifier.width(40.dp)) {
              Icon(imageVector = Icons.Filled.Delete, contentDescription = "")
            }
          } else {
            Spacer(modifier = Modifier.width(40.dp))
          }
        }

        if (options.isNotEmpty())
          ExposedDropdownMenu(
            expanded = expanded,
            modifier = Modifier.background(db_secondary_white),
            onDismissRequest = { expanded = false },
          ) {
            options.forEach { selectionOption ->
              DropdownMenuItem(
                modifier = Modifier
                  .background(db_secondary_white)
                  .exposedDropdownSize(true),
                onClick = {
                  contributor.internalId = selectionOption.internalId
                  contributor.userFullname.value = selectionOption.userFullname.value
                  contributor.userFullnameErrorMessage.value = ""
                  contributor.company.value = selectionOption.company.value
                  contributor.companyErrorMessage.value = ""
                  contributor.registrationNumber.value = selectionOption.registrationNumber.value
                  contributor.registrationNumberErrorMessage.value = ""
                  contributor.userCertificationNumber.value = selectionOption.userCertificationNumber.value
                  contributor.userCertificationNumberErrorMessage.value = ""
                  contributor.companyCertificationNumber.value = selectionOption.companyCertificationNumber.value
                  contributor.companyCertificationNumberErrorMessage.value = ""
                  contributor.id = selectionOption.id
                  contributor.entryId = selectionOption.entryId
                  contributor.createdBy = selectionOption.createdBy
                  contributor.creationDate = selectionOption.creationDate
                  expanded = false
                }
              ) {
                Column(modifier = Modifier.fillMaxWidth()) {
                  Row(verticalAlignment = CenterVertically) {
                    Text(text = selectionOption.userFullname.value ?: "")
                  }
                  // if there are users with the same name, we show additional information
                  if (options.groupBy { it.userFullname }.size > 1) {
                    Row(verticalAlignment = CenterVertically) {
                      Text(text = selectionOption.company.value ?: "")
                    }
                    Row(verticalAlignment = CenterVertically) {
                      Text(text = selectionOption.registrationNumber.value ?: "")
                    }
                    if (viewModel.shouldShowCertificatenummber()) {
                      Row(verticalAlignment = CenterVertically) {
                        Text(text = selectionOption.userCertificationNumber.value ?: "")
                      }
                    }
                    if (viewModel.shouldShowCertificatenummber()) {
                      Row(verticalAlignment = CenterVertically) {
                        Text(text = selectionOption.companyCertificationNumber.value ?: "")
                      }
                    }
                    DefaultDivider()
                    Spacer(modifier = Modifier.height(8.dp))
                  }
                }
              }
            }
          }
      }
    }
    ErrorComposable(contributor.userFullnameErrorMessage.value)
  }
  CustomTextField(
    label = R.string.logbook_entry_create_company_contributors,
    input = contributor.company,
    errorMessage = contributor.companyErrorMessage,
    enabled = enabled,
    shouldShow = true,
    mandatoryField = contributor.userFullname.value.isNotBlank(),
  )
  CustomTextField(
    label = R.string.logbook_entry_create_registrationnr_contributors,
    input = contributor.registrationNumber,
    errorMessage = contributor.registrationNumberErrorMessage,
    enabled = enabled,
    shouldShow = viewModel.logbook?.classEntity?.hasRegistrationNumber == true,
    mandatoryField = contributor.userFullname.value.isNotBlank(),
  )
  CustomTextField(
    label = R.string.logbook_entry_create_certification_numbers_contributors,
    input = contributor.userCertificationNumber,
    errorMessage = contributor.userCertificationNumberErrorMessage,
    enabled = enabled,
    shouldShow = viewModel.shouldShowCertificatenummber(),
    mandatoryField = contributor.userFullname.value.isNotBlank(),
  )
  CustomTextField(
    label = R.string.logbook_entry_create_company_certification_numbers_contributors,
    input = contributor.companyCertificationNumber,
    errorMessage = contributor.companyCertificationNumberErrorMessage,
    enabled = enabled,
    shouldShow = viewModel.shouldShowCertificatenummber(),
    mandatoryField = contributor.userFullname.value.isNotBlank(),
  )
}
2

There are 2 answers

0
c7n On BEST ANSWER

Since it seemed to be a focus issue with the text field inside of the ExposedDropdownMenuBox, I took a look at that specific class and found the part that caused the behaviour:

SideEffect {
    if (expanded) focusRequester.requestFocus()
}

So if expanded is not updated correctly and stays true, the view will regain the focus unintentionally.

Setting expanded to false when "leaving" the input field fixed the bevahiour:

ExposedDropdownMenuBox(
  expanded = expanded,
  onExpandedChange = { expanded = !expanded && enabled },
  modifier = Modifier.focusable(false)
  ) {
    Row(
      verticalAlignment = CenterVertically,
      horizontalArrangement = Arrangement.End,
      modifier = Modifier.fillMaxWidth().onFocusChanged { focusState: FocusState ->
        if (!focusState.hasFocus) {
          expanded = false
        }
      }
    )
    {
      OutlinedTextField()
      // ...
0
EliodeBeirut On

You can add inside the box containing the dropdown menu

val focusManager = LocalFocusManager.current

and a launched effect

  LaunchedEffect(key1 = expanded) {
        if (!expanded) focusManager.clearFocus(true)
    }