As I continue my exploration into Jetpack Compose I ran into an interesting problem. After tapping on a TextField and filling out the contents, the cursor continued to blink. It didn’t matter if I taped outside of the field or closed the virtual keyboard, the cursor just continued to blink. Which is interesting, because I don’t ever recall this behavior on the old view system. With the old view system tapping outside of the TextField caused the keyboard to minimize and the focus to change, which then resulted in the cursor ceasing to blink.
I’m uncertain whether this is a bug in the Jetpack Compose framework or if this is an intentional design. I have a hunch it is intentional, only due to the fact that is fairly easy to change the default focus behavior to something more tailored to your specific app.
Clearing the focus
If you are new to Android and unsure what I’m talking about when I mention focus, its explained a bit in the documentation on views. Essentially, in the android framework focus is just a boolean property on view. A view will have its focus updated if a user is interacting with it or if the framework determines that a view has either been removed, hidden or became available. So why is focus important, well one of the factors that determines whether the cursor should be blinking is if the view has focus. Even though we are using composables the concept seems to hold true for composables as well.
To clear the focus in a compose app we need to import the androidx.compose.ui.platform.LocalFocusManager value. After that you can get a reference to the current FocusManager interface and call clear focus.
import androidx.compose.ui.platform.LocalFocusManager
// get a copy of current focus manager
val focusManager = LocalFocusManager.current
// call clear focus
focusManager.clearFocus()
That’s it, after that the focus will be cleared. However, when do you call this?
When do you call focusManager.clearFocus()
A pattern that I like to use is to set both a keyboardOption and a keyboardAction on my TextField composable. You can pass in these objects as parameters to your TextField composable.
Lets take the keyboardOption first. In this object you can customize a few different options but the one we are interested in is imeAction. We want to set this property to ImeAction.Done. After that lets add the keyboardAction related to the Done option. This is completed by implementing the onDone keyboardAction. You can see the different options that you can customize here. Finally, lets take a look at the code for these changes below. Lines 15 – 17 are where we implement the option and action listed above, and we can see that clearFocus is being called by the onDone action.
@ExperimentalMaterial3Api
@Composable
fun TextFieldTests(){
var text by rememberSaveable() {
mutableStateOf("test")
}
val focusManager = LocalFocusManager.current
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
TextField(modifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = {text = it},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {
focusManager.clearFocus()
})
)
}
}
To illustrate what this looks like when the app runs take a look at the screenshots below. The first screenshot does not have any options or actions configured. The second screenshot is using the code listed above.
We can see after the change the keyboard now has a Done key as opposed to the return key, and when this ‘Done‘ key is pressed it will invoke the onDone action which then finally calls clearFocus().
There are lots of other actions that can be implemented, I only covered the onDone one. However, any other action should follow a very similar implementation as what was completed above.
-Cheers-