RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1600752
Accepted
Sirop4ik
Sirop4ik
Asked:2024-11-25 02:38:08 +0000 UTC2024-11-25 02:38:08 +0000 UTC 2024-11-25 02:38:08 +0000 UTC

如何正确使用 StateFlow 更新屏幕的 UIState

  • 772

我看到这篇文章

https://proandroiddev.com/loading-initial-data-in-launchedeffect-vs-viewmodel-f1747c20ce62

其中作者描述了数据初始化方法的优点和缺点,得出的结论是最好通过StateFlow.我决定尝试一下,前提是我使用UiState包装器作为屏幕的一般状态:

sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Error(val code: Int? = null, val message: String? = null) : UiState<Nothing>()
    data class Content<T>(val data: T) : UiState<T>()
}

也就是说,我在更改之前的实现:

class MyRepo {
    fun getMyData(): Flow<List<String>> {
        return flow {
            delay(1000)
            emit(listOf("1", "2"))
        }
    }
}

class MyViewModel1(repo: MyRepo): ViewModel() {
    data class ScreenStateUI(
        val data: List<String> = emptyList(),
        val title: String = "Title"
    )

    private val _screenUiState: MutableStateFlow<UiState<ScreenStateUI>> = MutableStateFlow(UiState.Loading)
    val screenUiState: StateFlow<UiState<ScreenStateUI>> = _screenUiState.asStateFlow()

    init {
        viewModelScope.launch {
            repo.getMyData()
                .map<List<String>, UiState<ScreenStateUI>> { UiState.Content(ScreenStateUI(data = it)) }
                .collectLatest {
                    if (it is UiState.Content) {
                        _screenUiState.emit(
                            UiState.Content(data = it)
                        )
                }
        }
    }

    fun updateTitle(title: String) {
        _screenUiState.update {
            if (it is UiState.Content) {
                it.copy(data = it.data.copy(title = title))
            } else {
                it
            }
        }
    }
}

初始化期间,状态被加载,UI可以通过订阅变化screenUiState,也就是说,如果需要使用它fun updateTitle(title: String),没有问题。

现在,我更改了实现以摆脱init块中的初始化,并在 UI 订阅事件后立即执行所有操作:

    class MyRepo {
        fun getMyData(): Flow<List<String>> {
            return flow {
                delay(1000)
                emit(listOf("1", "2"))
            }
        }
    }

class MyViewModel2(repo: MyRepo): ViewModel() {
    data class ScreenStateUI(
        val data: List<String> = emptyList(),
        val title: String = "Title"
    )

    val screenUiState: StateFlow<UiState<ScreenStateUI>> by lazy {
        repo.getMyData()
            .map<List<String>, UiState<ScreenStateUI>> { UiState.Content(ScreenStateUI(data = it)) }
            .onStart { emit(UiState.Loading) }
            .catch { emit(UiState.Error(message = it.message)) }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = UiState.Loading
            )
    }

    fun updateTitle(title: String) {
        ...
    }
}

也就是说,一切似乎都不错,实际上现在 UI 仅在订阅时才会发起请求,但现在状态无法更改,因此该方法fun updateTitle(title: String)无法更新title。

我在这里缺少什么?

android
  • 2 2 个回答
  • 28 Views

2 个回答

  • Voted
  1. Wlad
    2024-11-25T14:22:00Z2024-11-25T14:22:00Z

    你无法更新状态,因为......进行转型stateIn+选择策略SharingStarted.WhileSubscribed

    你的第一个选项更正确,因为可以在 ViewModel 内部更改状态。
    在您的示例中,您有一个状态,并且正在监听存储库中的一些流。
    如果您必须收听来自 2 个不同存储库的 2 个流怎么办?示例 2 已经开始变成一个带有陷阱的大型结构。

    而且我想说“ViewModel 仅在订阅时才执行请求”的想法与 ViewModel“独立于屏幕的生命周期而存在”的主要思想略有矛盾

    我并不完全清楚“您到底想在代码中更改什么(示例 1)以及为什么?”

    • 0
  2. Best Answer
    Sirop4ik
    2024-11-26T01:37:32Z2024-11-26T01:37:32Z

    最后这就是解决方案

    class MyViewModel2(repo: MyRepo) : ViewModel() {
        private val titleFlow = MutableStateFlow("Title")
    
        data class ScreenStateUI(
            val data: List<String>,
            val title: String,
        )
    
        init {
            viewModelScope.launch {
                delay(2000)
                updateTitle(title = "New title")
            }
        }
    
        val screenUiState: StateFlow<UiState<ScreenStateUI>> by lazy {
            combine(
                repo.getMyData(),
                titleFlow,
            ) { data, title ->
                Log.e("HERE", "combine: $data :: $title")
                UiState.Content<ScreenStateUI>(ScreenStateUI(data = data, title = title))
            }
                .onStart<UiState<ScreenStateUI>> { emit(UiState.Loading) }
                .catch<UiState<ScreenStateUI>> { emit(UiState.Error(message = it.message)) }
                .stateIn<UiState<ScreenStateUI>>(
                    scope = viewModelScope,
                    started = SharingStarted.WhileSubscribed(5000),
                    initialValue = UiState.Loading,
                )
        }
    
        fun updateTitle(title: String) {
            titleFlow.value = title
        }
    }
    
    Исполузуя `combine` можно сделать, то что нужно.
    
    • 0

相关问题

  • 来自片段的列表落后于 BottomNavigationView

  • 无法将变量从 Activity 传递到 Fragment

  • 构建与完成的片段略有不同的片段的最佳方法是什么?

  • 如何更改来自服务器的响应中的日期格式?

  • 谷歌地图在应用程序的发布版本中不起作用

  • 材料设计按钮。单击按钮上的可选区域!

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5