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

解决moko:biometry-compose桌面不兼容及@Composable调用报错问题

解决Compose中调用@Composable函数创建BiometryAuthenticator的错误

问题场景

使用dev.icerock.moko:biometry-compose库时,仅需在Android和iOS平台提供生物认证支持,桌面平台无需实现。通过expect/actual+接口的方案封装后,在LoginScreen的ViewModel工厂内调用createBiometryAuthenticator(),以及在BindBiometryAuthenticatorEffect中均触发错误:@composable invocations can only happen from the context of a @composable function。

错误原因

  1. ViewModel工厂无Composable上下文:viewModelFactory的lambda不属于@Composable函数,无法直接调用带@Composable注解的createBiometryAuthenticator()方法。
  2. 实例持有逻辑错位:试图让ViewModel持有需要Compose上下文创建的BiometryAuthenticator,违反了ViewModel与UI层解耦的设计原则。

解决方案

1. 调整实例创建位置,将Authenticator移至Composable层

在LoginScreen(本身是@Composable函数)中直接创建并持有BiometryAuthenticator实例:

@Composable
fun LoginScreen() {
    val biometryAuthenticatorFactory = BiometryAuthenticatorFactory()
    // 在Composable上下文内调用@Composable方法创建实例
    val biometryAuthenticator = biometryAuthenticatorFactory.createBiometryAuthenticator()
    
    val viewModel = getViewModel<AuthViewModel>(key = "biometry-screen")
    
    // 用LaunchedEffect监听ViewModel的认证请求事件
    LaunchedEffect(viewModel.authRequest) {
        viewModel.authRequest.collect { request ->
            val authResult = biometryAuthenticator.checkBiometryAuthentication(
                requestTitle = request.title,
                requestReason = request.reason,
                failureButtonText = request.failureText,
                allowDeviceCredentials = request.allowDeviceCreds
            )
            viewModel.onAuthResult(authResult)
        }
    }

    Column {
        // 界面元素示例
        Button(onClick = {
            viewModel.triggerBiometryAuth(
                title = "登录验证",
                reason = "请验证生物信息以登录",
                failureText = "使用密码登录",
                allowDeviceCreds = true
            )
        }) {
            Text(text = "生物识别登录")
        }
    }
}

2. 重构ViewModel,通过事件流触发认证

让ViewModel仅负责发送认证请求事件,不直接持有BiometryAuthenticator:

class AuthViewModel : ViewModel() {
    private val _authRequest = MutableSharedFlow<AuthRequest>()
    val authRequest = _authRequest.asSharedFlow()

    private val _authResult = MutableStateFlow<Boolean?>(null)
    val authResult = _authResult.asStateFlow()

    // 对外暴露触发认证的方法
    fun triggerBiometryAuth(
        title: String,
        reason: String,
        failureText: String,
        allowDeviceCreds: Boolean
    ) {
        viewModelScope.launch {
            _authRequest.emit(AuthRequest(title, reason, failureText, allowDeviceCreds))
        }
    }

    // 接收认证结果并更新状态
    fun onAuthResult(result: Boolean) {
        _authResult.value = result
    }

    data class AuthRequest(
        val title: String,
        val reason: String,
        val failureText: String,
        val allowDeviceCreds: Boolean
    )
}

3. 桌面平台提供空实现

在desktopMain目录下实现BiometryAuthenticatorFactory的空逻辑,避免编译错误:

actual class BiometryAuthenticatorFactory {
    @Composable
    actual fun createBiometryAuthenticator(): BiometryAuthenticator {
        return DesktopBiometryAuthenticator()
    }

    private class DesktopBiometryAuthenticator : BiometryAuthenticator {
        override suspend fun checkBiometryAuthentication(
            requestTitle: String,
            requestReason: String,
            failureButtonText: String,
            allowDeviceCredentials: Boolean
        ): Boolean {
            // 桌面平台不支持生物认证,返回默认结果或抛出异常(根据业务需求调整)
            return false
        }
    }
}

核心思路

  • 遵循ViewModel与UI层解耦原则:ViewModel不依赖Compose上下文,仅处理业务逻辑和事件传递。
  • 利用Composable上下文创建依赖UI环境的实例:BiometryAuthenticator需要rememberBiometryAuthenticatorFactory()这类Composable API,必须在Composable函数内创建。
  • 通过事件驱动实现ViewModel与UI层的交互:ViewModel发送请求,UI层执行具体操作并返回结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:04:58