繁体   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