State of Compose 2023 results are in! Click here to learn more
Published on

How to handle focus in Jetpack Compose with examples

Authors

Featured in Android Weekly #552

This guide will show you how to handle text focus in Jetpack Compose. There are different APIs responsible for different functionalities around text focus: the FocusRequester, the FocusManager and the various focus related Modifiers. We'll cover all of them and how to utilize them in your Jetpack Compose applications:

How to request text focus

In order to request focus and make the soft keyboard to show, you need access to the FocusRequester:

Column {
    val focusRequester = remember { FocusRequester() }

    var value by remember { mutableStateOf("") }

    TextField(
        modifier = Modifier.focusRequester(focusRequester),
        value = value,
        onValueChange = {
            value = it
        }
    )

    Button(onClick = {
        focusRequester.requestFocus()
    }) {
        Text("Gain focus")
    }
}

note how the FocusRequester instance is passed to the focusRequester() modifier of the TextField and then is used when the Button is clicked. The remember {} is used so that we do not create a new FocusRequester on every composition.

How to clear text focus

The FocusManager is the class which exposes APIs regarding controlling focus on the screen. Its clearFocus() function can be used to remove any focus like so:

val focusManager = LocalFocusManager.current
var value by remember { mutableStateOf("") }

TextField(
    value = value,
    onValueChange = {
        value = it
    }
)

LaunchedEffect(Unit) {
    while (true) {
        delay(2000)
        focusManager.clearFocus()
    }
}

in this example, the FocusManager will clear any focus from the screen every 2 seconds.

How to pass focus to the next composable

The FocusManager can only pass focus from one composable to the next. More specifically using the FocusManager.moveFocus() function and passing the direction you need:

Column {
    val focusManager = LocalFocusManager.current
    var value by remember { mutableStateOf("") }

    TextField(
        value = value,
        onValueChange = {
            value = it
        },
        singleLine = true,
        keyboardActions = KeyboardActions {
            focusManager.moveFocus(FocusDirection.Next)
        },
    )
    TextField(
        value = value,
        onValueChange = {
            value = it
        },
        singleLine = true,
        keyboardActions = KeyboardActions {
            focusManager.clearFocus()
        },
    )
}

In the above example, pressing the soft keyboard's IME action while the first TextField is pressed will move focus to the next TextField. Doing the same while the second TextField is focused, the focus will be cleared.

Passing a different FocusDirection to the moveFocus() function allows you to move towards any direction you need (such as backwards or up and down).

How to specify focus order

We saw how the FocusRequester is responsible for capturing focus and how you need to pass an instance of it to the composable's Modifier.

You can customize which composable's FocusRequester is next or previous by override its focusProperties using the focusProperties() modifier:

Row {
    val focusManager = LocalFocusManager.current
    Column {
        val (a, b, c) = FocusRequester.createRefs()
        TextField(
            modifier = Modifier
                .focusRequester(a)
                .focusProperties {
                    next = b
                },
            value = "",
            onValueChange = {},
        )
        TextField(
            modifier = Modifier
                .focusRequester(b)
                .focusProperties {
                    previous = a
                    next = c
                },
            value = "",
            onValueChange = {},
        )
        TextField(
            modifier = Modifier
                .focusRequester(c)
                .focusProperties {
                    previous = b
                },
            value = "",
            onValueChange = {},
        )
    }
    Column {
        Button(onClick = {
            focusManager.moveFocus(FocusDirection.Previous)
        }) {
            Text("Previous")
        }
        Button(onClick = {
            focusManager.moveFocus(FocusDirection.Next)
        }) {
            Text("Next")
        }
    }
}

The FocusRequester.createRefs() is a convinience function which creates multiple FocusRequesters instances which you can pass to multiple composables.

How to listen to focus changed events

There is a Modifier for that. Use the onFocusChanged { } Modifier. It provides a callback every time the focus state changes.

That was everything you need to know about focus in Jetpack Compose. We saw the three different parts of the focus API: the FocusRequester, the FocusManager and the related focus Modifiers. Depending on your requirements you might want to use them in combination to achieve the result you need.