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

相关推荐
饕餮争锋10 小时前
Kotlin: [Internal Error] java.lang.NoSuchFieldError: FILE_HASHING_STRATEGY
java·kotlin
用户693717500138410 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
G_dou_1 天前
KMP & OpenHarmony 实现二分查找
kotlin·鸿蒙
方白羽1 天前
Android多层嵌套RecyclerView滚动
android·java·kotlin
方白羽1 天前
Kotlin遇上Java 静态方法
android·java·kotlin
用户69371750013842 天前
11.Kotlin 类:继承控制的关键 ——final 与 open 修饰符
android·后端·kotlin
用户69371750013842 天前
10.Kotlin 类:延迟初始化:lateinit 与 by lazy 的对决
android·后端·kotlin
KotlinKUG贵州2 天前
SpringGateway-MVC对SSE转发出现阻塞响应问题的分析和解决
spring·spring cloud·kotlin
zhangphil2 天前
Android宽高不均等Bitmap缩放为指定宽高FitCenter到正方形Bitmap,Kotlin
android·kotlin
alexhilton2 天前
深入理解withContext和launch的真正区别
android·kotlin·android jetpack