簡體   English   中英

VIewModel observeAsState as Single event in Android Compose

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM