[英]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.