Compose下StateFlow误用导致输入跳变

问题复线

一段只有一个输入框的demo代码:

kotlin 复制代码
class SomeVM {
    val textState = MutableStateFlow("")

    fun update(text: String) {
        textState.value = text
    }
}

@Composable
@Preview
fun App() {
    MaterialTheme {
        val vm = remember(::SomeVM)
        val text by vm.textState.collectAsState()

        TextField(text, onValueChange = vm::update)
    }
}

运行后,连续快速按1、2两个按键,数次后发生输入错乱。

问题排查

上面示例使用了StateFlow,首先去掉StateFlow,换成Compose内置的State,即

kotlin 复制代码
class SomeVM {
    val textState = mutableStateOf("")

    fun update(text: String) {
        textState.value = text
    }
}

@Composable
@Preview
fun App() {
    MaterialTheme {
        val vm = remember(::SomeVM)
        val text by vm.textState

        TextField(text, onValueChange = vm::update)
    }
}

问题消失。原生的State没问题,StateFlow被大量使用有问题的概率也不大,因此看将StateFlow转换为State的方法:

原来collectAsState方法的参数context有默认值 EmptyCoroutineContext ,而其对应的 Dispatchers.Default 就是在异步线程在执行了。

所以问题就很明了了,用户输入和其他UI变化在主线程,而StateFlow的监听却到后台线程倒了一下手,主线程取到错误的文本长度导致焦点位置错误就是正常的了。

解决方案

解决的方法就是主动传入下Dispatchers.Main保证状态监听也在主线程就可以了。

kotlin 复制代码
class SomeVM {
    val textState = MutableStateFlow("")

    fun update(text: String) {
        textState.value = text
    }
}

@Composable
@Preview
fun App() {
    MaterialTheme {
        val vm = remember(::SomeVM)
        val text by vm.textState.collectAsState(Dispatchers.Main.immediate)

        TextField(text, onValueChange = vm::update)
    }
}

当然还要注意,Compose在JVM平台使用的Swing框架,需要添加依赖协程库才能知道哪个线程是主线程。

kotlin 复制代码
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")

更多相关

为何在VM中要把状态定义成StateFlow,直接用Compose的State不就啥事情都没有了?

定义成StateFlow可以方便地对数据进行一些操作,且保证VM中不包含任何UI代码,便于测试。

而且上面例子也是比较特殊的,大多数场景,点击XX按钮,不会像输入框这样有这么高的实时性要求。另外我个人认为,这里默认值用EmptyCoroutineContext是不合适的,就监听个状态还能把主线程累死吗。

相关推荐
Fushize8 小时前
多模块架构下的依赖治理:如何避免 Gradle 依赖地狱
android·架构·kotlin
Jomurphys9 小时前
Kotlin - 类型别名 typealias
android·kotlin
zh_xuan9 小时前
kotlin lazy委托异常时执行流程
开发语言·kotlin
我有与与症14 小时前
从页面加载过程看 Kuikly 的多线程架构
kotlin
JMchen12318 小时前
Android UDP编程:实现高效实时通信的全面指南
android·经验分享·网络协议·udp·kotlin
JMchen12319 小时前
Android网络安全实战:从HTTPS到双向认证
android·经验分享·网络协议·安全·web安全·https·kotlin
JMchen1231 天前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
儿歌八万首1 天前
硬核春节:用 Compose 打造“赛博鞭炮”
android·kotlin·compose·春节
消失的旧时光-19432 天前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed
有位神秘人2 天前
kotlin与Java中的单例模式总结
java·单例模式·kotlin