Preference Datastore 与 Flow 的坑
观察 DataStore<Preferences>
对象的 flow
却没有 distinctUntilChanged()
会导致一个 preference 更新所有的 flow
都发通知。
由来
最近给 Facemoji 加上了模糊的功能,但是却发现了一个问题:在 Emoji 模式下切换 Emoji 字体会导致切换前对 Emoji 做的新增和修改操作丢失。
而排查了一圈的直接原因是:选择不同的字体时,观察 mosaicMode
(Emoji /模糊)和 mosaicType
(高斯模糊/像素化/半色调)的观察者被意外触发了。
这个观察者预期的功能之一是用户在 Emoji 和模糊模式之间主动切换时,根据人脸识别的结果计算 Emoji 或着模糊区域应该出现的位置和大小以及方向。因此这个观察者被触发就会导致 Emoji 应该出现的位置、大小和方向重新被计算,导致修改的状态丢失。
罪魁祸首
实际上的问题出在 Datastore
的工作机制:对任何一个键值对的修改,都会使其 data
Flow 发射一个包含所有最新数据的全新对象。
而我在 PreferenceRepository
中所有设置项的 Flow
都是通过同一个 dataStore.data
进行 map
生成的。这也就导致对于字体设置项的修改触发了其他项目的更新(即使这个项目没有被更新),从而出现了修改丢失的情况。
解法
解决这个问题就要让设置项 A 更新时不触发其他设置项 B, C, D… 的更新,解法就是 distinctUntilChanged()
:如果两次状态更新值相同,那么其就会丢弃掉这次更新。
最后就是给每个 flow
对象加上了这个操作符:
val mosaicMode: Flow<Int> = dataStore.data.map { preferences ->
preferences[KEY_MOSAIC_MODE] ?: MOSAIC_MODE_EMOJI
}.distinctUntilChanged() // <--- 添加在这里