如何处理 Jetpack Compose 中的一次性操作?

Abh*_*bhi 15 android android-jetpack-compose

注意- 这个问题与这个问题几乎相同。
我正在寻找更好的方法(如果有的话)。

根据Android 文档

显示瞬时消息后,UI 需要通知 ViewModel,从而导致另一个 UI 状态更新:

例如,当我在单击按钮时显示 Toast 消息时,UI 是否应该通知 ViewModel Toast 已成功显示?
这是处理诸如吐司、小吃店等一次性操作的最佳方法吗?

示例代码,

@Composable
fun OneShotOperation(
    viewmodel: OneShotOperationViewModel = viewModel(),
) {
    val context = LocalContext.current

    LaunchedEffect(
        key1 = viewmodel.toastMessage,
    ) {
        if (viewmodel.toastMessage.isNotBlank()) {
            Toast.makeText(
                context,
                viewmodel.toastMessage,
                Toast.LENGTH_SHORT,
            ).show()
            viewmodel.toastMessage = "" // Is this the correct way?
        }
    }

    Button(
        onClick = {
            viewmodel.toastMessage = "Sample Toast"
        },
    ) {
        Text(text = "Show Toast")
    }
}

class OneShotOperationViewModel : ViewModel() {
    var toastMessage by mutableStateOf(
        value = "",
    )
}
Run Code Online (Sandbox Code Playgroud)

如果我删除这一行,
viewmodel.toastMessage = "" // Is this the correct way?
则 toast 仅显示一次,随后按下的按钮不会显示 toast,因为可变状态尚未更改。

Phi*_*hov 44

我更喜欢使用SharedFlow这样的工作。

请注意,如果您在未运行时从另一个视图发送消息collect,那么当您最终启动它时,它不会显示 toast。它不存储该值,仅将其传递给当前连接的所有收集器。

class OneShotOperationViewModel : ViewModel() {
    private val _toastMessage = MutableSharedFlow<String>()
    val toastMessage = _toastMessage.asSharedFlow()

    fun sendMessage(message: String) {
        viewModelScope.launch {
            _toastMessage.emit(message)
        }
    }
}

@Composable
fun TestScreen() {
    val context = LocalContext.current

    val viewModel = viewModel<OneShotOperationViewModel>()
    LaunchedEffect(Unit) {
        viewModel
            .toastMessage
            .collect { message ->
                Toast.makeText(
                    context,
                    message,
                    Toast.LENGTH_SHORT,
                ).show()
            }
    }
    Button(
        onClick = {
            viewModel.sendMessage("Sample Toast")
        },
    ) {
        Text(text = "Show Toast")
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @GastónSaillén 我认为这种方式更干净 - `collectAsState` 将触发重组,无论是在收到新消息时还是在删除消息时。另一方面,我的方法根本不会触发任何重组。您必须通过回调来删除旧事件,这是额外的代码行,并且在同一时间发送多条消息时更难以管理情况。我没有看到“collectAsState”有任何好处,但这取决于你。 (4认同)