解决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。
错误原因
- ViewModel工厂无Composable上下文:
viewModelFactory的lambda不属于@Composable函数,无法直接调用带@Composable注解的createBiometryAuthenticator()方法。 - 实例持有逻辑错位:试图让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
相关产品推荐
相关产品推荐

