[英]VIewModel observeAsState as Single event in Android Compose
我对视图 model 有问题,当我进行网络调用/viewModel 验证时,然后重新组合发生(例如 Textfield onValueChange),viewModel 持有对 viewModel 中最后一个“状态”的引用是“观察”的任何方式“ state 只有一次? 类似于SingleLiveEvent
还是我应该以某种方式从我的 viewModel 中“清除” state?
这是一个例子
@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))
}
}
}
}
}
我的主要问题是当我调用“验证电子邮件”然后我正在听var lceState = viewModel.lceState.observeAsState().value
这按预期工作,然后我想“清除”设置为 null 的错误,但是这被viewModel.lceState.observeAsState()
覆盖,因为在每个重新组合中它都保存最后一个值。 是否可以“观察”一次? 或撰写完成此事件后的“autoClear”?
非常感谢
一种选择是添加 Idle state 并将lceState
更改为此 state 以模拟没有发生任何事情。 当我不想显示初始加载 Composable 时,我使用这种方法
我建议使用 MVI 并使用 Event 来表示 ViewModel 的一次性数据以进行撰写。 例如:
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)
}
}
然后你可以这样做:
@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)) }
}
}
}
}
最好在 Screen 的最开始处理事件和动作,以避免将 ViewModel 作为 compose function 的参数。 这有助于测试和代码重用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.