Hey everyone, Since updating to Compose 1.8.0-alph...
# compose-desktop
t
Hey everyone, Since updating to Compose 1.8.0-alpha03, I’ve noticed that
ESC
on Desktop and
DPAD_BACK
on Android TV no longer behave as expected in
BasicTextField
and
TextField
. Previously, I handled these events manually to control focus and navigation, but now they seem to trigger an automatic back action, breaking focus management. The changelog mentions
PredictiveBackHandler
but only refers to iOS. Does this also affect Desktop? If so, is there a way to: 1. Access and control this new BackHandler API? 2. Disable it selectively for input fields? This is critical for proper navigation and text input handling. Any insights would be appreciated! Thanks!
a
Can you post a reproducer?
t
Copy code
@Composable
fun AppNavHost() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        graph = createNavGraph(navController),
        modifier = Modifier.fillMaxSize(),

    )
}


fun createNavGraph(navController: NavHostController): NavGraph {
    return navController.createGraph(startDestination = "screenA") {
        composable("screenA") {
            ScreenA (onNavigateToB = { navController.navigate("screenB") })
        }
        composable("screenB") {
            ScreenB (onNavigateBack = { navController.popBackStack() })
        }
    }
}
Copy code
@Composable
fun ScreenA(onNavigateToB: () -> Unit) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Screen A")

        Button(onClick = { onNavigateToB() }) {
            Text("Go to Screen B")
        }
    }
}@Composable
fun ScreenB(onNavigateBack: () -> Unit) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Screen B")
        Spacer(modifier = Modifier.height(16.dp))
        TestSearchBar(onNavigateBack=onNavigateBack)
    }
}

@Composable
fun TestSearchBar(onNavigateBack: () -> Unit) {
    val textState = remember { mutableStateOf("") }
    val focusRequesterOuter = remember { FocusRequester() }
    val focusRequesterInner = remember { FocusRequester() }
    val focusManager = LocalFocusManager.current
    var isTextFieldFocused by rememberSaveable { mutableStateOf(false) }

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .shadow(3.dp, RoundedCornerShape(8.dp))
            .background(if (isTextFieldFocused) Color.LightGray else Color.Gray)
            .focusRequester(focusRequesterOuter)
            .focusable()
            .onKeyEvent {
                if (it.type == KeyEventType.KeyDown) {
                    when (it.key) {
                        Key.Back, Key.Escape -> {
                            println("ESC/Back pressed at OUTER level")
                            onNavigateBack()
                            return@onKeyEvent true
                        }
                    }
                }
                false
            }
            .clickable { focusRequesterInner.requestFocus() }, // Clicking outer restores focus
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(Icons.Default.Search, contentDescription = "Search", modifier = Modifier.padding(8.dp))

        BasicTextField(
            value = textState.value,
            onValueChange = { textState.value = it },
            modifier = Modifier
                .weight(1f)
                .focusRequester(focusRequesterInner)
                .onFocusChanged { isTextFieldFocused = it.isFocused }
                .onKeyEvent {
                    if (it.type == KeyEventType.KeyDown) {
                        when (it.key) {
                            Key.Back, Key.Escape -> {
                                println("ESC/Back pressed at INNER level")
                                focusRequesterOuter.requestFocus() // Shift focus back to outer container
                                return@onKeyEvent true // Prevent system back behavior
                            }
                        }
                    }
                    false
                },
            singleLine = true,
            textStyle = TextStyle(color = Color.Black),
            keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
            keyboardActions = KeyboardActions(
                onSearch = { focusManager.clearFocus() }
            )
        )
    }

    LaunchedEffect(Unit) {
        focusRequesterInner.requestFocus() // Ensure field is focused at start
    }
}
It works as expected with
compose-multiplatform = 1.8.0-alpha02
,
androidx-lifecycle = 2.9.0-alpha02
, and
androidx-navigation = 2.8.0-alpha12
. However, with
compose-multiplatform = 1.8.0-alpha03
,
androidx-lifecycle = 2.9.0-alpha03
, and
androidx-navigation = 2.8.0-alpha13
, the
ESC
event unexpectedly triggers a navigation back from the inner level.
a
Thanks. We’ll look into it.
🙏 2
k
CMP-7569 [Desktop] Pressing Esc to close the dialog triggers navigation to the previous screen