简体   繁体   English

VIewModel observeAsState as Single event in Android Compose

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

相关问题 Android 与单个事件组合 - Android Compose with single event observeAsState 和 collectAsState 之间的区别以及何时在 Android Jetpack Compose 中使用它们? - Difference between observeAsState and collectAsState and when to use each in Android Jetpack Compose? Jetpack Compose - 未解决的参考:observeAsState - Jetpack Compose - Unresolved reference: observeAsState Android Compose - 如何处理 JetPackCompose 中的 ViewModel 清除焦点事件? - Android Compose - How to handle ViewModel clear focus event in JetPackCompose? 我得到“类型不匹配。 必需:State<string?> 使用 Jetpack 组合 viewModel.observeAsState() 时发现:字符串</string?> - I get “Type mismatch. Required: State<String?> Found: String” when Using viewModel.observeAsState() with Jetpack compose 喷气背包组成。 无限调用observeAsState - jetpack compose. infinite call to observeAsState 使用 viewModel 作为 jetpack compose 中的单一事实来源 - Using viewModel as the single source of truth in jetpack compose Android 组成 Navigation 和 ViewModel 无限网络调用 - Android compose Navigation and ViewModel infinite network calls Android Paging3 - 使用 Compose 从 ViewModel 刷新 - Android Paging3 - refresh from a ViewModel with Compose 如何访问在 Android Jetpack Compose 中注册到 MainActivity 的 ViewModel? - How to access ViewModel that is registered with MainActivity in Android Jetpack Compose?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM