Kotlin 协程 Kotlin Flow 和共享首选项从不调用 awaitClose

如何解决Kotlin 协程 Kotlin Flow 和共享首选项从不调用 awaitClose

我很想观察共同偏好的变化。以下是我使用 Kotlin Flow 的方法:

数据来源。

interface DataSource {

    fun bestTime(): Flow<Long>

    fun setBestTime(time: Long)
}

class LocalDataSource @Inject constructor(
    @ActivityContext context: Context
) : DataSource {

    private val preferences = context.getSharedPreferences(PREFS_FILE_NAME,Context.MODE_PRIVATE)

    @ExperimentalCoroutinesApi
    override fun bestTime() = callbackFlow {
        trySendBlocking(preferences,PREF_KEY_BEST_TIME)
        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences,key ->
            if (key == PREF_KEY_BEST_TIME) {
                trySendBlocking(sharedPreferences,key)
            }
        }
        preferences.registerOnSharedPreferenceChangeListener(listener)
        awaitClose { // NEVER CALLED
            preferences.unregisterOnSharedPreferenceChangeListener(listener)
        }
    }

    @ExperimentalCoroutinesApi
    private fun ProducerScope<Long>.trySendBlocking(
        sharedPreferences: SharedPreferences,key: String?
    ) {
        trySendBlocking(sharedPreferences.getLong(key,0L))
            .onSuccess { }
            .onFailure {
                Log.e(TAG,"",it)
            }
    }

    override fun setBestTime(time: Long) = preferences.edit {
        putLong(PREF_KEY_BEST_TIME,time)
    }

    companion object {
        private const val TAG = "LocalDataSource"

        private const val PREFS_FILE_NAME = "PREFS_FILE_NAME"
        private const val PREF_KEY_BEST_TIME = "PREF_KEY_BEST_TIME"
    }
}

存储库

interface Repository {

    fun observeBestTime(): Flow<Long>

    fun setBestTime(bestTime: Long)
}

class RepositoryImpl @Inject constructor(
    private val dataSource: DataSource
) : Repository {

    override fun observeBestTime() = dataSource.bestTime()

    override fun setBestTime(bestTime: Long) = dataSource.setBestTime(bestTime)
}

视图模型

class BestTimeViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(0L)
    val uiState: StateFlow<Long> = _uiState

    init {
        viewModelScope.launch {
            repository.observeBestTime()
                .onCompletion { // CALLED WHEN THE SCREEN IS ROTATED OR HOME BUTTON PRESSED
                    Log.d("myTag","viewModelScope onCompletion")
                }
                .collect { bestTime ->
                    _uiState.value = bestTime
                }
        }
    }

    fun setBestTime(time: Long) = repository.setBestTime(time)
}

片段。

@AndroidEntryPoint
class MetaDataFragment : Fragment(R.layout.fragment_meta_data) {

    @Inject
    lateinit var timeFormatter: TimeFormatter

    @Inject
    lateinit var bestTimeViewModel: BestTimeViewModel

    override fun onViewCreated(view: View,savedInstanceState: Bundle?) {
        super.onViewCreated(view,savedInstanceState)

        val bestTimeView = view.findViewById<TextView>(R.id.best_time_value)

        // Create a new coroutine in the lifecycleScope
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                bestTimeViewModel.uiState
                    .map { millis ->
                        timeFormatter.format(millis)
                    }
                    .onCompletion { // CALLED WHEN THE SCREEN IS ROTATED OR HOME BUTTON PRESSED
                        Log.d("MyApp","onCompletion")
                    }
                    .collect {
                        bestTimeView.text = it
                    }
            }
        }
    }
}

我注意到从未调用过 awaitClose。但这就是我的清理代码所在的地方。请指教。如果首先使用 callbackFlow 不是一个好主意,请告诉我(因为您可以看到一些函数是 ExperimentalCoroutinesApi 意味着它们的行为可以改变)

解决方法

问题是,您正在通过使用

注入您的 ViewModel,就好像它只是一个常规类一样
@Inject
lateinit var bestTimeViewModel: BestTimeViewModel

因此,永远不会取消 ViewModel 的 viewModelScope,因此永远收集 Flow。

根据 Documentation,您应该使用

privat val bestTimeViewModel: BestTimeViewModel by viewModels()

这可确保在您的 Fragment 销毁时调用 ViewModel 的 onCleared 方法,该方法将取消 viewModelScope

还要确保您的 ViewModel 使用 @HiltViewModel 进行注释:

@HiltViewModel
class BestTimeViewModel @Inject constructor(...) : ViewModel()
,

我找到了一个解决方案,可以让我保存一个简单的数据集(例如首选项)并使用 Kotlin Flow 观察其变化。它是Preferences DataStore。 这是我使用的代码实验室和指南: https://developer.android.com/codelabs/android-preferences-datastore#0 https://developer.android.com/topic/libraries/architecture/datastore

这是我的代码:

import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException

data class UserPreferences(val bestTime: Long)

private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

interface DataSource {

    fun userPreferencesFlow(): Flow<UserPreferences>

    suspend fun updateBestTime(newBestTime: Long)
}

class LocalDataSource(
    @ApplicationContext private val context: Context,) : DataSource {

    override fun userPreferencesFlow(): Flow<UserPreferences> =
        context.dataStore.data
            .catch { exception ->
                // dataStore.data throws an IOException when an error is encountered when reading data
                if (exception is IOException) {
                    emit(emptyPreferences())
                } else {
                    throw exception
                }
            }
            .map { preferences ->
                val bestTime = preferences[PreferencesKeys.BEST_TIME] ?: 0L
                UserPreferences(bestTime)
            }

    override suspend fun updateBestTime(newBestTime: Long) {
        context.dataStore.edit { preferences ->
            preferences[PreferencesKeys.BEST_TIME] = newBestTime
        }
    }
}

private object PreferencesKeys {
    val BEST_TIME = longPreferencesKey("BEST_TIME")
}

以及要添加到 build.gradle 的依赖项:

implementation "androidx.datastore:datastore-preferences:1.0.0"

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res