You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

获取联系人时触发@Composable调用错误,该如何解决?

解决Jetpack Compose中@Composable只能在@Composable上下文调用的错误

你遇到的错误提示:

@Composable invocations can only happen from the context of a @Composable function.

错误原因

你在rememberLauncherForActivityResult的回调函数中直接调用了LazyColumn(@Composable组件),但这个回调本身并没有被@Composable注解标记,不属于Composable上下文,违反了Jetpack Compose的核心规则——所有@Composable函数/组件必须在其他@Composable函数或带@Composable注解的Lambda中执行。

解决方案

通过状态变量分离业务逻辑与UI渲染:回调仅负责更新状态,UI渲染逻辑放在Composable的主流程中,依靠Compose的状态自动重组机制更新界面。

修改后的完整代码:

data class Contact(val name: String, val phoneNumber: String)

@Composable
fun ContactList(){
    val context = LocalContext.current
    // 状态变量:存储获取到的联系人列表
    val contactsState = remember { mutableStateOf<List<Contact>>(emptyList()) }
    // 状态变量:标记是否已获取联系人权限
    val hasPermissionState = remember { mutableStateOf(false) }

    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
        if (isGranted) {
            // 权限通过,仅更新状态变量
            val contacts = fetchContacts(context)
            contactsState.value = contacts
            hasPermissionState.value = true
        } else {
            // 权限被拒绝,更新状态
            hasPermissionState.value = false
        }
    }

    // 首次启动时申请权限,避免重组重复发起
    LaunchedEffect(Unit) {
        launcher.launch(Manifest.permission.READ_CONTACTS)
    }

    // 根据状态渲染对应UI
    when {
        !hasPermissionState.value -> {
            Text(text = "需要联系人读取权限才能显示联系人")
            // 可添加重新申请权限的按钮
        }
        contactsState.value.isEmpty() -> {
            Text(text = "暂无联系人数据")
        }
        else -> {
            LazyColumn {
                items(contactsState.value) { contact ->
                    Text(text = "Name: ${contact.name}, Phone: ${contact.phoneNumber}")
                    Spacer(modifier = Modifier.padding(10.dp))
                }
            }
        }
    }
}

private fun fetchContacts(context: Context): List<Contact> {
    val contacts = mutableListOf<Contact>()
    val cursor = context.contentResolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null,
        null,
        null,
        null
    )

    cursor?.use {
        while (it.moveToNext()) {
            val name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
            val phoneNumber = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
            contacts.add(Contact(name, phoneNumber))
        }
    }

    return contacts
}

关键修改点

  • 新增contactsState和hasPermissionState两个状态变量,用remember { mutableStateOf(...) }包裹,确保Composable重组时状态不丢失
  • 将原回调中的UI渲染逻辑移到Composable主流程,通过when表达式根据状态切换界面
  • 用LaunchedEffect(Unit)包裹权限申请逻辑,避免组件重组时重复发起权限请求
  • 回调仅负责更新状态,不再直接调用@Composable组件

内容的提问来源于stack exchange,提问作者indiebloog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:50:34