如何在使用 Kotlin 时组合流?

huangapple go评论79阅读模式
英文:

How can I combine Flows when I use Kotlin?

问题

以下是已翻译的部分:

Code A

private val _audioRecordState = MutableStateFlow(ERecordState.STOPPED)
private val _listSortBy = MutableStateFlow(ESortBy.START_PRIORITY)
private val _listMInfo = _listSortBy.map { handelMInfo.listAll(it)}   // 它返回 Flow<Flow<EResult<List<MInfo>>>>

val homeUIState: StateFlow<HomeUIState> =  combine(
    _audioRecordState, _listSortBy, _listMInfo
)
{    audioRecordState, listSortBy ,listMInfo->

     log("A: ")
     val temp= listMInfo.last()
     log("B: ")  // 它不触发

     when (temp) {
         is EResult.LOADING -> {
             HomeUIState(audioRecordState, listSortBy)
         }
         is EResult.SUCCESS -> {
             log("C: "+ temp.data.size)
             HomeUIState(audioRecordState, listSortBy, temp.data)
         }
         is EResult.ERROR -> {
             HomeUIState(audioRecordState, listSortBy)
         }
     }
}
    .stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(),
        HomeUIState(audioRecordState = ERecordState.STOPPED)
    )


data class HomeUIState(
    val audioRecordState: ERecordState = ERecordState.STOPPED,
    val listSortBy: ESortBy = ESortBy.START_PRIORITY,
    val listMInfo: List<MInfo> = listOf<MInfo>()
)

fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>>

Code B

private val _savedFilterType =
        savedStateHandle.getStateFlow(TASKS_FILTER_SAVED_STATE_KEY, ALL_TASKS)

private val _filterUiInfo = _savedFilterType.map { getFilterUiInfo(it) }.distinctUntilChanged()
private val _userMessage: MutableStateFlow<Int?> = MutableStateFlow(null)
private val _isLoading = MutableStateFlow(false)
private val _filteredTasksAsync =
    combine(taskRepository.getTasksStream(), _savedFilterType) { tasks, type ->
        filterTasks(tasks, type)
    }
        .map { Async.Success(it) }
        .catch<Async<List<Task>>> { emit(Async.Error(R.string.loading_tasks_error)) }

val uiState: StateFlow<TasksUiState> = combine(
    _filterUiInfo, _isLoading, _userMessage, _filteredTasksAsync
) { filterUiInfo, isLoading, userMessage, tasksAsync ->
    when (tasksAsync) {
        Async.Loading -> {
            TasksUiState(isLoading = true)
        }
        is Async.Error -> {
            TasksUiState(userMessage = tasksAsync.errorMessage)
        }
        is Async.Success -> {
            TasksUiState(
                items = tasksAsync.data,
                filteringUiInfo = filterUiInfo,
                isLoading = isLoading,
                userMessage = userMessage
            )
        }
    }
}
    .stateIn(
        scope = viewModelScope,
        started = WhileUiSubscribed,
        initialValue = TasksUiState(isLoading = true)
    )

希望这有所帮助!如果您有任何其他翻译需求,请随时告诉我。

英文:

The Code B comes from the official sample project , it generate a UI state with combine Flows.

I hope to create a UI state using Code A, but it fails, how can I fix it ?

The most differences between Code A and Code B is fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>> which requires a MutableStateFlow parameter ESortBy , and I have to collect Flow within combine().

Code A

    private val _audioRecordState= MutableStateFlow(ERecordState.STOPPED)
    private val _listSortBy = MutableStateFlow(ESortBy.START_PRIORITY)
    private val _listMInfo = _listSortBy.map { handelMInfo.listAll(it)}   // It returns  Flow<Flow<EResult<List<MInfo>>>>

    val homeUIState: StateFlow<HomeUIState> =  combine(
        _audioRecordState, _listSortBy, _listMInfo
    )
    {    audioRecordState, listSortBy ,listMInfo->

         log("A: ")
         val temp= listMInfo.last()
         log("B: ")  // It doesn't fire

         when (temp) {
             is EResult.LOADING -> {
                 HomeUIState(audioRecordState, listSortBy)
             }
             is EResult.SUCCESS -> {
                 log("C: "+ temp.data.size)
                 HomeUIState(audioRecordState, listSortBy, temp.data)
             }
             is EResult.ERROR -> {
                 HomeUIState(audioRecordState, listSortBy)
             }
         }
    }
        .stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(),
            HomeUIState(audioRecordState = ERecordState.STOPPED)
        )


data class HomeUIState(
    val audioRecordState: ERecordState = ERecordState.STOPPED,
    val listSortBy: ESortBy = ESortBy.START_PRIORITY,
    val listMInfo: List<MInfo> = listOf<MInfo>()
)

fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>>

Code B

private val _savedFilterType =
        savedStateHandle.getStateFlow(TASKS_FILTER_SAVED_STATE_KEY, ALL_TASKS)

    private val _filterUiInfo = _savedFilterType.map { getFilterUiInfo(it) }.distinctUntilChanged()
    private val _userMessage: MutableStateFlow<Int?> = MutableStateFlow(null)
    private val _isLoading = MutableStateFlow(false)
    private val _filteredTasksAsync =
        combine(taskRepository.getTasksStream(), _savedFilterType) { tasks, type ->
            filterTasks(tasks, type)
        }
            .map { Async.Success(it) }
            .catch<Async<List<Task>>> { emit(Async.Error(R.string.loading_tasks_error)) }

    val uiState: StateFlow<TasksUiState> = combine(
        _filterUiInfo, _isLoading, _userMessage, _filteredTasksAsync
    ) { filterUiInfo, isLoading, userMessage, tasksAsync ->
        when (tasksAsync) {
            Async.Loading -> {
                TasksUiState(isLoading = true)
            }
            is Async.Error -> {
                TasksUiState(userMessage = tasksAsync.errorMessage)
            }
            is Async.Success -> {
                TasksUiState(
                    items = tasksAsync.data,
                    filteringUiInfo = filterUiInfo,
                    isLoading = isLoading,
                    userMessage = userMessage
                )
            }
        }
    }
        .stateIn(
            scope = viewModelScope,
            started = WhileUiSubscribed,
            initialValue = TasksUiState(isLoading = true)
        )

答案1

得分: 1

调用 Flow 上的 last() 会使其挂起,以便可以收集整个流,直到它声明自己完成,然后返回最后收集的值。如果在 SharedFlow、StateFlow 或任何无限冷流上调用 last,它将 永远 不会返回,因为这些流永远不会完成。

在我看来,你不应该传递流的流。流的流只是一个临时对象,你可以使用它来通过某种方式展平它来构建更终的流,例如通过调用 flatMapConcatflatMapLatesttransformLatest 等方法。在展平它之前传递流的流会令人困惑,因为在代码的其他部分不清楚如何正确处理它。

如果在合并之前展平你的流,那么在 combine lambda 内部处理它将会更容易。

你应该在这里正确地展平你的流:

private val _listMInfo = _listSortBy.map { handelMInfo.listAll(it)}   // 它返回 Flow<Flow<EResult<List<MInfo>>>>
    .foo(...) // 这里应使用某种流操作符来适当地展平它,具体取决于你的情况。

我不知道你的应用程序的设计,无法告诉你展平这个特定流的正确方法。

英文:

Calling last() on a Flow makes it suspend so it can collect the entire flow until it declares itself complete, and then it returns the last value collected. If you call last on a SharedFlow or StateFlow or any infinite cold Flow, it will never return because these flows never complete.

In my opinion, you should not be passing around Flows of Flows. A Flow of Flows is just a temporary object you use to build a more final flow by flattening it somehow, by calling something like flatMapConcat or flatMapLatest or transformLatest on it, etc. It's confusing to pass the Flow of Flows around before flattening it, because then it's unclear in other parts of the code how you should properly handle it.

If you flatten your Flow before combining it, then it will be much easier to work with inside the combine lambda.

You should properly flatten your flow right here:

private val _listMInfo = _listSortBy.map { handelMInfo.listAll(it)}   // It returns  Flow<Flow<EResult<List<MInfo>>>>
    .foo(...) // some flow operator here to flatten it appropriately, depending on your situation.

I don't know the design of your app to be able to tell you what is the proper way to flatten this particular flow.

huangapple
  • 本文由 发表于 2023年6月8日 09:30:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76428050.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定