Search code examples
kotlinandroid-jetpack-composeandroid-jetpackandroid-jetpack-compose-material3android-compose-textfield

How to make sure the phone keyboard is always hidden when pressing on BasicTextField?


I need my calculator BasicTextField to show the cursor but not the keyboard. Currently, when I tap the BasicTextField for the first time, the cursor appears, but the keyboard does not. When I tap it a second time, the keyboard appears

Previously, I used CompositionLocalProvider(LocalTextInputService provides null), but it's now deprecated.

This is how my current code looks like:

BoxWithConstraints(
            modifier = modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            val containerSize = minOf(maxWidth, maxHeight)
            val fontSize = containerSize * 0.25f
            val spacing = containerSize * 0.01f
            val allowedCharacters = "0123456789+-×÷−%.,"

            val keyboardController = LocalSoftwareKeyboardController.current
            val focusRequester = remember { FocusRequester() }
            val interactionSource = remember { MutableInteractionSource() }

            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collect { interaction ->
                    if (interaction is PressInteraction.Release) {
                        focusRequester.requestFocus()
                    }
                }
            }
            DisposableEffect(Unit) {
                focusRequester.requestFocus()
                keyboardController?.hide()
                onDispose { }
            }

            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Bottom
            ) {
                BasicTextField(
                    value = viewModel.currentExpression.value,
                    onValueChange = { newValue ->
                        // Filter the input to allow only the specified characters
                        val filteredValue = newValue.filter { it in allowedCharacters }
                        viewModel.currentExpression.value = filteredValue
                    },
                    singleLine = true,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 8.dp, horizontal = 16.dp)
                        .focusRequester(focusRequester)
                        .onPreviewKeyEvent { keyEvent ->
                            if (keyEvent.type == KeyEventType.KeyDown &&
                                (keyEvent.key == Key.Enter || keyEvent.key == Key.NumPadEnter)) {
                                keyboardController?.hide()
                                true
                            } else {
                                false
                            }
                        },
                    maxLines = 1,
                    textStyle = TextStyle(
                        fontSize = fontSize.value.sp,
                        textAlign = TextAlign.End,
                        color = MaterialTheme.colorScheme.onSurface,
                        letterSpacing = spacing.value.sp
                    ),
                    keyboardOptions = KeyboardOptions.Default.copy(
                        imeAction = ImeAction.None,
                        keyboardType = KeyboardType.Number
                    ),
                    cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface),
                    interactionSource = interactionSource
                )
            }
        }

How can I ensure that the keyboard remains hidden while the cursor stays visible in the BasicTextField?


Solution

  • There is KeyboardOptions.showKeyboardOnFocus property but it seems to be ignored by the current BasicTextField implementations.

    Try clearing the focus on each press and then refocusing on release while hiding the keyboard on a focus change:

    BoxWithConstraints(
        modifier = modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        val containerSize = minOf(maxWidth, maxHeight)
        val fontSize = containerSize * 0.25f
        val spacing = containerSize * 0.01f
        val allowedCharacters = "0123456789+-×÷−%.,"
    
        val keyboardController = LocalSoftwareKeyboardController.current
        val focusManager = LocalFocusManager.current
        val focusRequester = remember { FocusRequester() }
        val interactionSource = remember { MutableInteractionSource() }
        val isPressed by interactionSource.collectIsPressedAsState()
        var refocus by rememberSaveable { mutableStateOf(false) }
        LaunchedEffect(isPressed) {
            if (isPressed) {
                focusManager.clearFocus()
                refocus = true
            } else if (refocus) {
                focusRequester.requestFocus()
                refocus = false
            }
        }
    
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Bottom
        ) {
            var text by remember { mutableStateOf("Text") }
    
            BasicTextField(
                value = viewModel.currentExpression.value,
                onValueChange = { newValue ->
                    // Filter the input to allow only the specified characters
                    val filteredValue = newValue.filter { it in allowedCharacters }
                    viewModel.currentExpression.value = filteredValue
                },
                singleLine = true,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 8.dp, horizontal = 16.dp)
                    .focusRequester(focusRequester)
                    .onFocusChanged {
                        keyboardController?.hide()
                    },
                textStyle = TextStyle(
                    fontSize = fontSize.value.sp,
                    textAlign = TextAlign.End,
                    color = MaterialTheme.colorScheme.onSurface,
                    letterSpacing = spacing.value.sp
                ),
                keyboardOptions = KeyboardOptions.Default.copy(
                    imeAction = ImeAction.None,
                    keyboardType = KeyboardType.Number
                ),
                cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface),
                interactionSource = interactionSource
            )
        }
    }