20

I'm using TextField component in Jetpack Compose. How to select all text when it receive focus?

nglauber
  • 18,674
  • 6
  • 70
  • 75
any_ktokolwiek
  • 201
  • 2
  • 3

5 Answers5

14

In this case you should use TextFieldValue as state of your TextField, and when it receive focus, you set the selection using the TextFieldValue state.

val state = remember {
    mutableStateOf(TextFieldValue(""))
}
TextField(
    value = state.value,
    onValueChange = { text -> state.value = text },
    modifier = Modifier
        .onFocusChanged { focusState ->
            if (focusState.isFocused) {
                val text = state.value.text
                state.value = state.value.copy(
                    selection = TextRange(0, text.length)
                )
            }
        }
)

Here's the result:

enter image description here

Notice that depending on you're touching the cursor goes to the touched position instead of select the entire text. You can try to figure it out if this is a bug or a feature :)

nglauber
  • 18,674
  • 6
  • 70
  • 75
7

@nglauber solution doesn't seems to work anymore.

Debugging shows that onFocusChanged is called before onValueChange and within one view life cycle. A selection changed during onFocusChanged has no effect on TextField, since it is overridden during onValueChange.

Here's a possible workaround:

var state by remember {
    mutableStateOf(TextFieldValue("1231"))
}
var keepWholeSelection by remember { mutableStateOf(false) }
if (keepWholeSelection) {
    // in case onValueChange was not called immediately after onFocusChanged
    // the selection will be transferred correctly, so we don't need to redefine it anymore
    SideEffect {
        keepWholeSelection = false
    }
}
TextField(
    value = state,
    onValueChange = { newState ->
        if (keepWholeSelection) {
            keepWholeSelection = false
            state = newState.copy(
                selection = TextRange(0, newState.text.length)
            )
        } else {
            state = newState
        }
    },
    modifier = Modifier
        .onFocusChanged { focusState ->
            if (focusState.isFocused) {
                val text = state.text
                state = state.copy(
                    selection = TextRange(0, text.length)
                )
                keepWholeSelection = true
            }
        }
)

I think it should be possible to make it easier, so I created this question on Compose issue tracker.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
2

I didn't have 100% success with @nglauber answer. You should add a small delay and it works great. For example:

        val state = remember {
            mutableStateOf(TextFieldValue(""))
        }

        // get coroutine scope from composable
        val scope = rememberCoroutineScope()

        TextField(
            value = state.value,
            onValueChange = { text -> state.value = text },
            modifier = Modifier
                .onFocusChanged {
                    if (it.hasFocus) {
                        // start coroutine
                        scope.launch {
                            // add your preferred delay
                            delay(10)
                            val text = state.value.text
                            state.value = state.value.copy(
                                selection = TextRange(0, text.length)
                            )
                        }
                    }
                }
        )
F.Mysir
  • 2,838
  • 28
  • 39
  • 1
    I also had the issue of this not working consistently. I am using `imePadding` and I'm wondering if that's messing with it. I put in a 100ms delay and it fixed the issue. – Blake Nov 05 '22 at 23:33
2

I wrote a Modifier extension function that works in spite of bug pointed out by @Pylyp

fun Modifier.onFocusSelectAll(textFieldValueState: MutableState<TextFieldValue>): Modifier =
  composed(
    inspectorInfo = debugInspectorInfo {
      name = "textFieldValueState"
      properties["textFieldValueState"] = textFieldValueState
    }
  ) {
    var triggerEffect by remember {
      mutableStateOf<Boolean?>(null)
    }
    if (triggerEffect != null) {
      LaunchedEffect(triggerEffect) {
        val tfv = textFieldValueState.value
        textFieldValueState.value = tfv.copy(selection = TextRange(0, tfv.text.length))
      }
    }
    Modifier.onFocusChanged { focusState ->
      if (focusState.isFocused) {
        triggerEffect = triggerEffect?.let { bool ->
          !bool
        } ?: true
      }
    }
  }

usage

@Composable
fun SelectAllOnFocusDemo() {
  var tfvState = remember {
    mutableStateOf(TextFieldValue("initial text"))
  }
  TextField(
    modifier = Modifier.onFocusSelectAll(tfvState),
    value = tfvState.value,
    onValueChange = { tfvState.value = it },
  )
}
Tom Berghuis
  • 491
  • 3
  • 10
  • 26
1

I want to add to the Phil's answer I wanted to update the state dynamically and I ended up with this:

var state by remember(textVal) {
    mutableStateOf(TextFieldValue(text = textVal, selection = TextRange(textVal.length)))
}

It does two things, first it updates the field if your textVal changes, also puts the cursor at the end.

David Aleksanyan
  • 2,953
  • 4
  • 29
  • 39