I have a TextField that is displayed when the user clicks on the search button, and the visibility is animated. The problem is that when I request the focus to show the keyboard, the composition is not finished, so focusRequester was not initialized, causing the crash. One possible solution would be to add a delay with the same amount of time as the animation but this is not safe as it could have a racing condition and the crash happen in rare cases. Any other alternative?
@Composable
fun MyComponent() {
var expanded by rememberSaveable { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }
AnimatedVisibility(
visible = expanded,
enter = expandHorizontally(animationSpec = tween(300)) + fadeIn(),
exit = shrinkHorizontally(animationSpec = tween(300)) + fadeOut()
) {
TextField(
modifier = Modifier.focusRequester(focusRequester),
...)
}
IconButton(onClick = {
expanded = !expanded
focusRequester.requestFocus()
}) {
MyImage(
icon = R.drawable.ic_search,
modifier = Modifier.size(56.dp)
)
}
}
java.lang.IllegalStateException:
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
So instead of requesting the focus immediately after setting the expanded state to true
, you should wait for the animation transition to complete first to ensure your TextField
has been launched into composition, before requesting focus.
A handy tool to do this with is the MutableTransitionState
which you can use to know the current animation state of the AnimatedVisibility. In using this, you can put it in a LaunchedEffect and request the focus after the transition is completed.
Here's your re-written example below:
@Composable
fun MyComponent() {
val visibilityState = remember { MutableTransitionState(false) }
val focusRequester = remember { FocusRequester() }
AnimatedVisibility(
visibleState = visibilityState,
enter = expandHorizontally(animationSpec = tween(300)) + fadeIn(),
exit = shrinkHorizontally(animationSpec = tween(300)) + fadeOut()
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
value = "",
onValueChange = {},
)
}
IconButton(onClick = {
visibilityState.targetState = !visibilityState.currentState
}) {
Icon(
modifier = Modifier.size(56.dp),
imageVector = Icons.Outlined.Search,
contentDescription = "Search"
)
}
LaunchedEffect(visibilityState.currentState) {
if (visibilityState.isIdle && visibilityState.currentState) {
focusRequester.requestFocus()
println("Focus requested on TextField")
}
}
}
You can find more examples on using MutableTransitionState
here