Search code examples
androidkotlinandroid-jetpack-composeaccessibilitytalkback

How to request first item in a list to be focused when you want it to be on top (Jetpack compose)?


By pressing one of 2 buttons, I can add a text "Apple" or "Orange" at the start of a list, in which that list will be displayed in a column on top. Each new text will appear at the top of the list.

    val myList = remember { mutableStateListOf<String>() }
    val focus = remember { FocusRequester() }

    Column {
        myList.forEachIndexed { index, word ->
            Text(
                text = word,
                modifier =
                if (index == 0) {
                    Modifier
                        .focusRequester(focus)
                        .focusable()
                } else {
                    Modifier
                }
            )
        }

        if (myList.isNotEmpty()) {
            focus.requestFocus()
        }

        Button(
            onClick = {
                myList.add("Apple")
            },
            modifier = Modifier.padding(top = 8.dp)
        ) {
            Text("Add Apple")
        }

        Button(
            onClick = {
                myList.add("Orange")
            },
            modifier = Modifier.padding(top = 8.dp)
        ) {
            Text("Add Orange")
        }
    }

This doesn't work for some reason.

Out of curiosity, I checked to see what would happen if I changed it so that the last item would be focused:

                if (index == myList.size -1) {
                    Modifier
                        .focusRequester(focus)
                        .focusable()
                } else {
                    Modifier
                }

When trying that, the accessibility focus goes to the last text, at the bottom of the list.

So why is it that I the focus only works if it's the last item of the list, but not the first (or any item before)?

Is there a way to fix it?


Solution

  • To trigger talkback you should refocus a composable by clearing focus first with FocusManager.clearFocus(). It can be done by launching a coroutine in buttons' onClick handlers.

        val myList = remember { mutableStateListOf<String>() }
        val focusRequester = remember { FocusRequester() }
        val focusManager = LocalFocusManager.current
        val scope = rememberCoroutineScope()
    
        Column {
            myList.forEachIndexed { index, word ->
                var color by remember { mutableStateOf(Black) }
                Text(
                    text = word,
                    modifier =
                    if (index == 0) {
                        Modifier
                            .focusRequester(focusRequester)
                            .focusable()
                    } else {
                        Modifier
                    }
                )
            }
            fun refocus() = scope.launch {
                delay(50)
                focusManager.clearFocus()
                delay(50)
                focusRequester.requestFocus()
            }
            Button(
                onClick = {
                    myList.add(0, "Apple")
                    refocus()
                },
                modifier = Modifier.padding(top = 8.dp)
            ) {
                Text("Add Apple")
            }
    
            Button(
                onClick = {
                    myList.add(0, "Orange")
                    refocus()
                },
                modifier = Modifier.padding(top = 8.dp)
            ) {
                Text("Add Orange")
            }
        }
    

    There should be a small delay before requestFocus() in LaunchedEffect just to be sure that it executes after focusRequester modifier.