StateFlow Observer 被触发两次 - 这是一个好的解决方案吗?
StateFlow Observer is being triggered Two Times - Is this a good solution?
好的,我已经将 StateFlow 与 Room 数据库一起使用了一段时间。现在我有一个常见的情况。在我的应用程序开始时,我有一个逻辑,如果 ROOM 数据库为空,我应该显示一个 EmptyContent(),否则我将显示 ROOM 数据库中的 ListContent()。
现在每次我启动应用程序时,我总是会看到 EmptyContent() 显示半秒,然后显示 ListContent()。之后,当我使用该应用程序时,一切正常。但是在那个应用程序启动时,当 ROOM 数据库正在工作时,我猜 EmptyContent() 只显示了一小段时间(因为我的 StateFlow 默认值是一个空列表),然后显示了数据库中的实际 LIST .
现在我有一个解决方案,只需在协程中使用 delay() 函数,例如等待 200MS,然后触发读取数据库的函数,因为这 200MS 足以让 ROOM 数据库实际获取值并用实际数据更新我的 STATE FLOW 变量,而不是在开始时使用半秒的 StateFlow 默认值。
这是一个很好的解决方案,我必须问一下?因为我使用协程,线程没有被阻塞,我只是在等待 ROOM 数据库第二次更新我的 STATE FLOW 变量。
@Composable
fun displayContent(
tasks: List<ToDoTask>,
ListContent: @Composable () -> Unit
) {
val scope = rememberCoroutineScope()
var counter by remember { mutableStateOf(0)}
LaunchedEffect(Unit){
scope.launch {
delay(200)
counter = 1
}
}
if(counter == 1){
if (tasks.isNotEmpty()) {
ListContent()
} else {
EmptyContent()
}
}
}
我的建议是映射您的预期状态。
例如:
sealed class RequestState<out T> {
object Idle : RequestState<Nothing>()
object Loading : RequestState<Nothing>()
data class Success<T>(val data: T) : RequestState<T>()
data class Error(
val t: Throwable,
var consumed: Boolean = false
) : RequestState<Nothing>()
}
你的函数应该是这样的:
@Composable
fun YourScreen() {
val requestState = viewModel.screenData.collectAsState()
when (requestState) {
is Idle ->
// This is the default state, do nothing
is Loading ->
// Display some progress indicator
is Success ->
YourListScreen(requestState.data) // Show the list
is Error ->
// Display an error.
}
LaunchedEffect(Unit) {
viewModel.loadData()
}
}
当然,在您的视图模型中,您必须正确发出这些值...
class YourView: ViewModel() {
private val _screenData =
MutableStateFlow<RequestState<List<ToDoTask>>>(RequestState.Idle)
val screenDatae: StateFlow<RequestState<List<ToDoTask>>> = _screenData
fun loadData() {
_screenData.value = Loading
try {
// load the data from database
_screenData.value = Success(yourLoadedData)
} catch (e: Exception) {
_screenData.value = Error(e)
}
}
}
好的,我已经将 StateFlow 与 Room 数据库一起使用了一段时间。现在我有一个常见的情况。在我的应用程序开始时,我有一个逻辑,如果 ROOM 数据库为空,我应该显示一个 EmptyContent(),否则我将显示 ROOM 数据库中的 ListContent()。
现在每次我启动应用程序时,我总是会看到 EmptyContent() 显示半秒,然后显示 ListContent()。之后,当我使用该应用程序时,一切正常。但是在那个应用程序启动时,当 ROOM 数据库正在工作时,我猜 EmptyContent() 只显示了一小段时间(因为我的 StateFlow 默认值是一个空列表),然后显示了数据库中的实际 LIST .
现在我有一个解决方案,只需在协程中使用 delay() 函数,例如等待 200MS,然后触发读取数据库的函数,因为这 200MS 足以让 ROOM 数据库实际获取值并用实际数据更新我的 STATE FLOW 变量,而不是在开始时使用半秒的 StateFlow 默认值。
这是一个很好的解决方案,我必须问一下?因为我使用协程,线程没有被阻塞,我只是在等待 ROOM 数据库第二次更新我的 STATE FLOW 变量。
@Composable
fun displayContent(
tasks: List<ToDoTask>,
ListContent: @Composable () -> Unit
) {
val scope = rememberCoroutineScope()
var counter by remember { mutableStateOf(0)}
LaunchedEffect(Unit){
scope.launch {
delay(200)
counter = 1
}
}
if(counter == 1){
if (tasks.isNotEmpty()) {
ListContent()
} else {
EmptyContent()
}
}
}
我的建议是映射您的预期状态。 例如:
sealed class RequestState<out T> {
object Idle : RequestState<Nothing>()
object Loading : RequestState<Nothing>()
data class Success<T>(val data: T) : RequestState<T>()
data class Error(
val t: Throwable,
var consumed: Boolean = false
) : RequestState<Nothing>()
}
你的函数应该是这样的:
@Composable
fun YourScreen() {
val requestState = viewModel.screenData.collectAsState()
when (requestState) {
is Idle ->
// This is the default state, do nothing
is Loading ->
// Display some progress indicator
is Success ->
YourListScreen(requestState.data) // Show the list
is Error ->
// Display an error.
}
LaunchedEffect(Unit) {
viewModel.loadData()
}
}
当然,在您的视图模型中,您必须正确发出这些值...
class YourView: ViewModel() {
private val _screenData =
MutableStateFlow<RequestState<List<ToDoTask>>>(RequestState.Idle)
val screenDatae: StateFlow<RequestState<List<ToDoTask>>> = _screenData
fun loadData() {
_screenData.value = Loading
try {
// load the data from database
_screenData.value = Success(yourLoadedData)
} catch (e: Exception) {
_screenData.value = Error(e)
}
}
}