[英]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()方法。
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()
)
}
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()
//...
}
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.