Jetpack Compose UI测试出现FocusRequester未初始化IllegalStateException如何解决
问题原因
- 该报错是测试环境和正式运行环境的Compose渲染时序差异导致的:正式环境中,
FocusRequester通过Modifier.focusRequester()绑定到TextField可聚焦节点的时序,早于LaunchedEffect的执行时机,因此请求焦点能正常触发。而测试环境中LaunchedEffect的执行时机更早,会在FocusRequester完成绑定前就调用requestFocus(),此时FocusRequester还未关联到任何可聚焦节点,就会抛出未初始化的异常。 - 另外Compose UI测试默认关闭了部分窗口焦点自动获取逻辑,也会加剧该问题的出现概率。
解决方案
以下三种方案均可解决问题,可根据场景选择:
方案1:修改业务代码,等待布局完成后再请求焦点
在LaunchedEffect中先等待一帧布局完成,再发起焦点请求,修改后代码如下:
@Composable fun ScreenView( data: ScreenViewData, ) { val focusRequester = remember { FocusRequester() } LaunchedEffect(key1 = Unit) { // 等待一帧,确保Modifier绑定完成,也可替换为delay(10) 实现相同效果 awaitFrame() focusRequester.requestFocus() } Scaffold() { innerPadding -> Column { OutlinedTextField( modifier = Modifier.focusRequester(focusRequester), value = "", onValueChange = {} ) } } }
注:awaitFrame() 需要引入依赖 androidx.compose.ui:ui-platform
方案2:修改业务代码,布局完成回调触发焦点请求
通过onGloballyPositioned监听TextField的布局完成事件,只有控件成功渲染到视图树后才触发焦点请求,稳定性更高:
@Composable fun ScreenView( data: ScreenViewData, ) { val focusRequester = remember { FocusRequester() } var hasRequestedFocus by remember { mutableStateOf(false) } Scaffold() { innerPadding -> Column { OutlinedTextField( modifier = Modifier .focusRequester(focusRequester) .onGloballyPositioned { if (!hasRequestedFocus) { focusRequester.requestFocus() hasRequestedFocus = true } }, value = "", onValueChange = {} ) } } }
方案3:仅修改测试代码,无需改动业务逻辑
在测试的setContent执行后,先等待UI完全渲染空闲,再执行后续断言:
@ExperimentalAnimationApi class ScreenViewTest { @get:Rule val composeTestRule = createComposeRule() @ExperimentalMaterialApi @Test fun ScreenViewElementsAreDisplayed() { composeTestRule.setContent { MyAppTheme { ScreenView( data = ScreenViewData(), ) } } // 等待UI合成、布局全部完成 composeTestRule.waitForIdle() // 后续断言逻辑 } }
如果上述方案还是报错,可以在测试代码中额外给根布局请求一次焦点:
composeTestRule.onRoot().performSemanticsAction(SemanticsActions.RequestFocus) composeTestRule.waitForIdle()
内容的提问来源于stack exchange,提问作者Abhimanyu
相关产品推荐
相关产品推荐

