[英]VIewModel observeAsState as Single event in Android Compose
I have an issue with view model, when I do a network call/viewModel validation, and then the re composition happens (for example Textfield onValueChange) the viewModel holds the reference to the last "state" from the viewModel is any way to "observe" the state only once?我对视图 model 有问题,当我进行网络调用/viewModel 验证时,然后重新组合发生(例如 Textfield onValueChange),viewModel 持有对 viewModel 中最后一个“状态”的引用是“观察”的任何方式“ state 只有一次? similar to
SingleLiveEvent
or should I "clear" the state from my viewModel somehow?类似于
SingleLiveEvent
还是我应该以某种方式从我的 viewModel 中“清除” state?
here is an Example这是一个例子
@Composable
fun TestViewModelStateCompose(viewModel: TestViewModel) {
Column(modifier = Modifier.fillMaxSize()) {
var email by remember { mutableStateOf("") }
var error by remember { mutableStateOf<String?>(null) }
var lceState = viewModel.lceState.observeAsState().value
when (lceState) {
is Lce.Content -> {
// do something
if(lceState.result.not()){
error = "Wrong email :/"
}
}
is Lce.Error -> {
error = "Wrong email"
}
Lce.Loading -> {
//loading
}
null -> {}
}
TextField(
value = email, onValueChange = {
email = it
error = null
},
modifier = Modifier.fillMaxWidth()
)
error?.let {
Text(
text = it,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.error,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 8.dp)
.fillMaxWidth(),
maxLines = 1
)
}
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
viewModel.validateEmail()
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp)
) {
Text(
text = "Validate email",
)
}
}
}
sealed class Lce {
object Loading : Lce()
data class Content(val result: Boolean) : Lce()
data class Error(val error: Throwable) : Lce()
}
class TestViewModel() : ViewModel() {
val lceState = SingleLiveEvent<Lce>()
private val isValid = false
fun validateEmail() {
if (isValid) {
// do something
} else {
viewModelScope.launch {
//place holder, the email is always invalid for test
flowOf(false)
.onStart {
// simulate call
delay(1000)
lceState.postValue(Lce.Loading)
}
.catch { lceState.postValue(Lce.Error(it)) }
.collect {
lceState.postValue(Lce.Content(it))
}
}
}
}
}
My main issue is when i call "validate emaial" and then I'm listening the var lceState = viewModel.lceState.observeAsState().value
this works as expected, then I want to "clear" the error just setting to null, however this is being overriden by viewModel.lceState.observeAsState()
because in every re composition it holds the last value.我的主要问题是当我调用“验证电子邮件”然后我正在听
var lceState = viewModel.lceState.observeAsState().value
这按预期工作,然后我想“清除”设置为 null 的错误,但是这被viewModel.lceState.observeAsState()
覆盖,因为在每个重新组合中它都保存最后一个值。 is possible to "observe" as only one time?是否可以“观察”一次? or "autoClear" after the compose comsume this events?
或撰写完成此事件后的“autoClear”?
Thanks a lot非常感谢
One option for doing would be adding an Idle state and changing lceState
to this state to simulate nothing is happening.一种选择是添加 Idle state 并将
lceState
更改为此 state 以模拟没有发生任何事情。 I use this approach when i don't want to show an initial loading Composable当我不想显示初始加载 Composable 时,我使用这种方法
I recommend using MVI and using Event to represent the ViewModel's one-time data for compose.我建议使用 MVI 并使用 Event 来表示 ViewModel 的一次性数据以进行撰写。 For example:
例如:
interface Event
abstract class BaseViewModel<E : Event> : ViewModel() {
private val _event = Channel<E>()
val event = _event.receiveAsFlow().shareIn(viewModelScope, SharingStarted.Lazily)
protected suspend fun sendEvent(event: E) = _event.send(event)
protected fun sendEventSync(event: E) = viewModelScope.launch { _event.send(event) }
}
@Composable
fun <E : Event> OnEvent(event: Flow<E>, onEvent: (E) -> Unit) {
LaunchedEffect(Unit) {
event.collect(onEvent)
}
}
Then you can do this:然后你可以这样做:
@Composable
fun TestViewModelStateCompose(viewModel: TestViewModel) {
Column(modifier = Modifier.fillMaxSize()) {
var email by remember { mutableStateOf("") }
var error by remember { mutableStateOf<String?>(null) }
OnEvent(viewModel.event) {
when (it) {
is TestEvent.ValidateEmailFailure -> error = "Wrong email :/"
is TestEvent.ValidateEmailLoading -> Unit // loading
TestEvent.ValidateEmailSuccess -> Unit // do something like toast
}
}
TextField(
value = email, onValueChange = {
email = it
error = null
},
modifier = Modifier.fillMaxWidth()
)
error?.let {
Text(
text = it,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.error,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 8.dp)
.fillMaxWidth(),
maxLines = 1
)
}
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
viewModel.validateEmail()
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp)
) {
Text(
text = "Validate email",
)
}
}
}
sealed interface TestEvent : Event {
object ValidateEmailLoading : TestEvent
object ValidateEmailSuccess : TestEvent
data class ValidateEmailFailure(val exception: Throwable) : TestEvent
}
class TestViewModel : BaseViewModel<TestEvent>() {
private val isValid = false
private suspend fun simulateValidate(): Result<Unit> {
return runCatching {
delay(1000)
}
}
fun validateEmail() {
if (isValid) {
// do something
} else {
viewModelScope.launch {
sendEvent(TestEvent.ValidateEmailLoading)
simulateValidate()
.onSuccess { sendEvent(TestEvent.ValidateEmailSuccess) }
.onFailure { sendEvent(TestEvent.ValidateEmailFailure(it)) }
}
}
}
}
It is better to handle Events and Actions at the very beginning of the Screen to avoid taking the ViewModel as a parameter of compose function.最好在 Screen 的最开始处理事件和动作,以避免将 ViewModel 作为 compose function 的参数。 This helps with testing and code reuse.
这有助于测试和代码重用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.