Duq*_*uqe 14 events android state snackbar android-jetpack-compose
现在我在 ViewModel 中有一个 Event 类,它以 Flow 的方式公开:
abstract class BaseViewModel() : ViewModel() {
...
private val eventChannel = Channel<Event>(Channel.BUFFERED)
val eventsFlow = eventChannel.receiveAsFlow()
fun sendEvent(event: Event) {
viewModelScope.launch {
eventChannel.send(event)
}
}
sealed class Event {
data class NavigateTo(val destination: Int): Event()
data class ShowSnackbarResource(val resource: Int): Event()
data class ShowSnackbarString(val message: String): Event()
}
}
Run Code Online (Sandbox Code Playgroud)
这是管理它的可组合项:
@Composable
fun SearchScreen(
viewModel: SearchViewModel
) {
val events = viewModel.eventsFlow.collectAsState(initial = null)
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(all = 24.dp)
) {
SearchHeader(viewModel = viewModel)
SearchContent(
viewModel = viewModel,
modifier = Modifier.padding(top = 24.dp)
)
when(events.value) {
is NavigateTo -> TODO()
is ShowSnackbarResource -> {
val resources = LocalContext.current.resources
val message = (events.value as ShowSnackbarResource).resource
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getString(message)
)
}
}
is ShowSnackbarString -> {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = (events.value as ShowSnackbarString).message
)
}
}
}
}
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
Run Code Online (Sandbox Code Playgroud)
我从这里开始使用 Flow 遵循单个事件的模式。
我的问题是,该事件仅在第一次时才能正确处理(SnackBar 正确显示)。但在那之后,似乎不再收集事件了。至少在我离开屏幕再回来之前是这样。在这种情况下,所有事件都会连续触发。
看不出我做错了什么。调试时,事件会正确发送到通道,但状态值似乎未在可组合项中更新。
小智 21
不要将逻辑放在可组合项中,而是将它们放在可组合项中
// Runs only on initial composition
LaunchedEffect(key1 = Unit) {
viewModel.eventsFlow.collectLatest { value ->
when(value) {
// Handle events
}
}
}
Run Code Online (Sandbox Code Playgroud)
而且不是将其用作状态,而是从LaunchedEffect块中的流中收集价值。这就是我在应用程序中实现单个事件的方式
Sor*_*tfi 13
这是杰克答案的修改版本,作为遵循新准则的扩展函数,以实现更安全的流量收集。
@Composable
inline fun <reified T> Flow<T>.observeWithLifecycle(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
noinline action: suspend (T) -> Unit
) {
LaunchedEffect(key1 = Unit) {
lifecycleOwner.lifecycleScope.launch {
flowWithLifecycle(lifecycleOwner.lifecycle, minActiveState).collect(action)
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
viewModel.flow.observeWithLifecycle { value ->
//Use the collected value
}
Run Code Online (Sandbox Code Playgroud)
我不确定你如何编译代码,因为我在launch.
对 launch 的调用应该发生在 LaunchedEffect 内部,而不是合成内部
通常你可以使用LaunchedEffect已经在协程作用域中运行的 which ,所以你不需要coroutineScope.launch. 阅读文档中有关副作用的更多信息。
一些 kotlin 建议:when在类型中使用时,不需要手动将变量转换为带有as. 在这种情况下,您可以val与变量一起声明以防止Smart cast to ... is impossible, because ... is a property that has open or custom getter错误:
val resources = LocalContext.current.resources
val event = events.value // allow Smart cast
LaunchedEffect(event) {
when (event) {
is BaseViewModel.Event.NavigateTo -> TODO()
is BaseViewModel.Event.ShowSnackbarResource -> {
val message = event.resource
snackbarHostState.showSnackbar(
message = resources.getString(message)
)
}
is BaseViewModel.Event.ShowSnackbarString -> {
snackbarHostState.showSnackbar(
message = event.message
)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码有一个问题:如果多次发送同一事件,则不会显示该事件,因为LaunchedEffect不会重新启动:event因为密钥相同。
您可以通过不同的方式解决这个问题。这里是其中的一些:
替换data class为class:现在事件将通过指针而不是字段进行比较。
向数据类添加一个随机 id,以便每个新元素不等于另一个:
data class ShowSnackbarResource(val resource: Int, val id: UUID = UUID.randomUUID()) : Event()
Run Code Online (Sandbox Code Playgroud)
LaunchedEffect请注意,当新事件发生时,协程将被取消。由于它showSnackbar是一个暂停功能,因此前一个小吃栏将被隐藏以显示新的小吃栏。如果你继续运行showSnackbar(coroutineScope.launch仍在内部执行LaunchedEffect),新的小吃栏将等到前一个小吃栏消失后再出现。
另一种选择对我来说似乎更干净,是重置事件的状态,因为您已经对其做出了反应。您可以添加另一个事件来执行此操作:
object Clean : Event()
Run Code Online (Sandbox Code Playgroud)
并在snackbar消失后发送:
LaunchedEffect(event) {
when (event) {
is BaseViewModel.Event.NavigateTo -> TODO()
is BaseViewModel.Event.ShowSnackbarResource -> {
val message = event.resource
snackbarHostState.showSnackbar(
message = resources.getString(message)
)
}
is BaseViewModel.Event.ShowSnackbarString -> {
snackbarHostState.showSnackbar(
message = event.message
)
}
null, BaseViewModel.Event.Clean -> return@LaunchedEffect
}
viewModel.sendEvent(BaseViewModel.Event.Clean)
}
Run Code Online (Sandbox Code Playgroud)
但在这种情况下,如果您在前一个事件尚未消失时发送相同的事件,它将像以前一样被忽略。这可能是完全正常的,具体取决于应用程序的结构,但为了防止这种情况,您可以像以前一样显示它coroutineScope。
另外,请查看JetNews撰写应用程序示例中实现的更通用的解决方案。我建议您下载该项目并从显示小吃栏的位置开始检查它。
| 归档时间: |
|
| 查看次数: |
15324 次 |
| 最近记录: |