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是不合适的,就监听个状态还能把主线程累死吗。

相关推荐
金銀銅鐵1 天前
一个 4 个常量的枚举,Kotlin 在字节码里生成了什么
jvm·kotlin
Easyhood3 天前
Android 开发入门实践(十四):Compose 副作用入门与四个常用 API
android·开发语言·kotlin·android jetpack·compose
松仔log3 天前
Kotlin中级——callbackFlow
java·前端·kotlin
zh_xuan3 天前
kotlin常见面试题
开发语言·kotlin·面试题
mmsx3 天前
耳切法切带洞多边形:论文能跑通,真实数据为什么崩
android·kotlin
Kapaseker3 天前
Kotlin 又要加语法糖了:这次轮到 if-else
android·kotlin
雨白4 天前
从原理到实践:使用 Kotlin KSP 仿写 ButterKnifeLite
android·kotlin
灿飞4 天前
Kotlin与Java虚拟机十一:类与属性:构造顺序、backing field 与显式 backing field
java·后端·kotlin
传奇开心果4 天前
【探秘 Jetpack Compose 框架深水区】 第8课:rememberSaveable 的边界在哪里?——Bundle、Saver 与什么值得跨进程存活
android·kotlin