简体   繁体   English

如何在 Jetpack Compose 的 viewModel 内将 DataStore 首选项的 Flow 转换为 StateFlow

[英]How can I convert DataStore preferences' Flow to StateFlow inside a viewModel in Jetpack Compose

I'm trying to implement a "settings" screen for my app using DataStore pereferences.我正在尝试使用 DataStore 参考为我的应用程序实现“设置”屏幕。 Basically, an url is created, according to the preferences, so a WebView can load it later.基本上,根据偏好创建一个 url,因此 WebView 可以稍后加载它。 The data is saved correctly, and the UI from the settings screen is updating the values when a preference is changed.数据已正确保存,设置屏幕中的 UI 会在首选项更改时更新值。

The problem is that I found out, when trying to load an updated url, that the preferences are not read instantly (the WebView was loading a previous url value after being updated).问题是我发现,在尝试加载更新的 url 时,不会立即读取首选项(WebView 在更新后加载以前的 url 值)。 So my question is how to convert it to that type so the preferences are read in real time?所以我的问题是如何将其转换为该类型以便实时读取首选项?

Here are some code snippets:以下是一些代码片段:

UserPreferences Data class用户偏好数据 class

data class UserPreferences(
    val conexionSegura: Boolean,
    val servidor: String,
    val puerto: String,
    val pagina: String,
    val parametros: String,
    val empresa: String,
    val inicioDirecto: Boolean,
    val usuario: String,
    val password: String,
    val url: String
)

DataStore repository impl getPreferences() DataStore 存储库实现 getPreferences()

class DataStoreRepositoryImpl @Inject constructor(
    private val dataStore: DataStore<Preferences>
) : DataStoreRepository {


    override suspend fun getPreferences() =
        dataStore.data
            .map { preferences ->
                UserPreferences(
                    conexionSegura = preferences[PreferencesKeys.CONEXION_SEGURA] ?: false,
                    servidor = preferences[PreferencesKeys.SERVIDOR] ?: "",
                    puerto = preferences[PreferencesKeys.PUERTO] ?: "",
                    pagina = preferences[PreferencesKeys.PAGINA] ?: "",
                    parametros = preferences[PreferencesKeys.PARAMETROS] ?: "",
                    empresa = preferences[PreferencesKeys.EMPRESA] ?: "",
                    inicioDirecto = preferences[PreferencesKeys.INICIO_DIRECTO] ?: false,
                    usuario = preferences[PreferencesKeys.USUARIO] ?: "",
                    password = preferences[PreferencesKeys.PASSWORD] ?: "",
                    url = preferences[PreferencesKeys.URL] ?: CONTENT_URL
                )
            }
...
...

ViewModel readPreferences()视图模型 readPreferences()

 private val _state = mutableStateOf(SettingsState())

    val state: State<SettingsState> = _state

    init {
        readPreferences()
        updateUrl()
    }

    private fun readPreferences() {
        viewModelScope.launch {
            dataStoreRepositoryImpl.getPreferences().collect {
                _state.value = state.value.copy(
                    conexionSegura = it.conexionSegura,
                    servidor = it.servidor,
                    puerto = it.puerto,
                    pagina = it.pagina,
                    parametros = it.parametros,
                    empresa = it.empresa,
                    inicioDirecto = it.inicioDirecto,
                    usuario = it.usuario,
                    password = it.password
                )
            }
        }
    }
...
...

After investigating and reading a little bit, I realised that DataStore emits flows, not stateFlows, which would be needed in compose.在调查和阅读了一些内容之后,我意识到 DataStore 发出流,而不是 stateFlows,这在 compose 中是需要的。 I thought about deleting the UserPreferences class, and simply read one preference at a time, collecting it asState inside the screen composable.我考虑过删除 UserPreferences class,一次只读取一个偏好,将其收集为屏幕可组合项中的状态。 But, I think the code would be cleaner of I don't do that:) I really like the idea of using a separate class to hold the preferences' values但是,我认为代码会更清晰,因为我不这样做 :) 我真的很喜欢使用单独的 class 来保存首选项值的想法

When you want to transform a Flow into a StateFlow in a ViewModel to be consumed in the view, the correct way is using the stateIn() method.当您想要将 ViewModel 中的Flow转换为StateFlow以在视图中使用时,正确的方法是使用stateIn()方法。

Use of stateIn状态输入的使用

Assuming you have the following class and interface:假设你有以下 class 和接口:

class SettingsState()

sealed interface MyDatastore {
    fun getPreferences(): Flow<SettingsState>
}

In your viewModel, create a val that will use the datatStore.getPreferences method and transform the flow into a stateFlow using stateIn在您的 viewModel 中,创建一个将使用 datatStore.getPreferences 方法的 val,并使用 stateIn 将流转换为 stateFlow

class MyViewModel(
    private val dataStore: MyDatastore
) : ViewModel() {

    val state: StateFlow<SettingsState> = dataStore
        .getPreferences()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = SettingsState()
        )
}

Collect in the composable在可组合项中收集

To start getting preferences, you have just to collect the stateFlow as state:要开始获取首选项,您只需将 stateFlow 收集为 state:

@Composable
fun MyComposable(
    myViewModel: MyViewModel = hiltViewModel()
) {
    
    val state = myViewModel.state.collectAsState()
    
    //...
    
}

Pros优点

As you can see, you don't need to use init in the ViewModel .如您所见,您不需要在ViewModel中使用init Like 90% of time, use of init is not required.像 90% 的时间一样,不需要使用init The ViewModel became more testable because you don't need to mock everything that is in the init block. ViewModel 变得更易于测试,因为您不需要模拟init块中的所有内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在视图模型中从 DataStore 首选项中获取值,如 Flow - How to get a value from DataStore preferences, as Flow, within viewmodel 如何在 Jetpack compose 中正确使用 StateFlow? - How to properly use StateFlow with Jetpack compose? 如何在 Jetpack Compose 中将 sheetPeekHeight 设置为其中项目的高度? - How can I set sheetPeekHeight to the height of an item inside it in Jetpack Compose? Jetpack Compose 与 Coroutine 的 StateFlow - Jetpack Compose with Coroutine's StateFlow 如何在 Jetpack Compose 中使用 Viewmodel - How to use Viewmodel with Jetpack compose 如果我可以将 Flow 和 StateFlow 与生命周期范围 \ viewLifecycleOwner.lifecycleScope 一起使用,那么在 ViewModel 中使用 LiveData 有什么意义 - What's the point of using LiveData in ViewModel if I can use Flow and StateFlow with lifecycleScope \ viewLifecycleOwner.lifecycleScope 如何从 jetpack compose 中的 viewModel 更改 TextField 值中的值 - How can i change value in TextField value from viewModel in jetpack compose 如何在 Jetpack Compose 中将视图模型从一个屏幕共享到另一个屏幕? - How can I share viewmodel from one screen to another screen in jetpack compose? LazyColumn 中的 Jetpack Compose 视图模型初始化 - Jetpack Compose viewmodel initialisation inside LazyColumn 如何根据 Jetpack Compose 中的流程获取 Gps 按钮状态 - How can I get Gps button status based on flow in the Jetpack Compose
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM