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

203 Views Asked by At

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?

1

There are 1 best solutions below

2
Jan Itor On BEST ANSWER

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.