以下代码实现了一个简单的屏幕,该屏幕显示了使用带有Kotlin协程的MVI模式的用户列表。该代码将运行,但不会产生任何结果。经过大量调试后,我发现 UserListVM 中的 getUsers 函数没有发出任何内容。希望在这里有所帮助。非常感谢。

    class UserListVM : ViewModel() {

    val resultFlows: Channel<Flow<*>> = Channel(Channel.UNLIMITED)
    val liveState = MutableLiveData<PModel<*, UserListIntents>>()
    val intents: Channel<UserListIntents> = Channel()

    lateinit var job: Job
    lateinit var currentState: UserListState

    fun offer(event: UserListIntents) = intents.offer(event)

    suspend fun store(initialState: UserListState): LiveData<PModel<*, UserListIntents>> {
        job = viewModelScope.launch {
            currentState = initialState
            intents.consumeEach { intent ->
                resultFlows.send(reduceIntentsToResults(intent, currentState)
                        .flowOn(Executors.newFixedThreadPool(4).asCoroutineDispatcher())
                        .map { SuccessResult(it, intent) }
                        .catch { ErrorEffectResult(it, intent) }
                        .onStart { emit(LoadingEffectResult(intent)) }
                        .distinctUntilChanged()
                )
            }
            resultFlows.consumeEach { results ->
                results.flatMapMerge {
                    val states = stateStream(this as Flow<Result<UserListResult, UserListIntents>>, currentState)
                    val effects = effectStream(this as Flow<Result<UserListEffect, UserListIntents>>)
                    flowOf(states, effects)
                }
                        .flattenMerge()
                        .collect { pModel -> liveState.value = pModel }
            }
        }
        job.start()
        return liveState
    }

    private suspend fun reduceIntentsToResults(intent: UserListIntents, currentState: Any): Flow<*> {
        Log.d("UserListVM", "currentStateBundle: $currentState")
        return when (intent) {
            is GetPaginatedUsersIntent -> when (currentState) {
                is EmptyState, is GetState -> getUsers()
                else -> throwIllegalStateException(intent)
            }
            is UserClickedIntent -> when (currentState) {
                is GetState -> flowOf((SuccessEffectResult(NavigateTo(intent.user), intent)))
                else -> throwIllegalStateException(intent)
            }
        }
    }

    private suspend fun getUsers(): Flow<UsersResult> {
        return flow {
            emit(UsersResult(listOf(User("user1", 1), User("user2", 2), User("user3", 3))))
        }.flowOn(Dispatchers.IO)
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
    }

    class UserListActivity : AppCompatActivity() {

    var intentStream: Flow<UserListIntents> = flowOf()
    lateinit var viewModel: UserListVM
    lateinit var viewState: UserListState
    private lateinit var usersAdapter: GenericRecyclerViewAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initialize()
        setupUI(savedInstanceState == null)
        viewModel.store(viewState).observe(this, Observer {
            it?.apply {
                when (this) {
                    is ErrorEffect -> bindError(errorMessage, error, intent)
                    is SuccessEffect -> bindEffect(bundle as UserListEffect)
                    is SuccessState -> {
                        (bundle as UserListState).also { state ->
                            viewState = state
                            bindState(state)
                        }
                    }
                }
                toggleLoadingViews(intent)
            }
        })
    }

    fun initialize() {
        viewModel = getViewModel()
        viewState = EmptyState()
    }

    fun setupUI(isNew: Boolean) {
        setContentView(R.layout.activity_user_list)
        setSupportActionBar(toolbar)
        toolbar.title = title
        setupRecyclerView()
    }

    override fun onResume() {
        super.onResume()
        if (viewState is EmptyState) {
            GlobalScope.launch {
                viewModel.offer(GetPaginatedUsersIntent(0))
            }
        }
    }

    private fun bindState(successState: UserListState) {
        usersAdapter.setDataList(successState.list, successState.callback)
    }

    private fun bindEffect(effectBundle: UserListEffect) {
        when (effectBundle) {
            is NavigateTo -> {
               // ..
            }
        }
    }

    fun bindError(errorMessage: String, cause: Throwable, intent: UserListIntents) {
        //..
    }

    private fun setupRecyclerView() {
        usersAdapter = object : GenericRecyclerViewAdapter() {
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<*> {
                return when (viewType) {
                    R.layout.empty_view -> EmptyViewHolder(layoutInflater
                            .inflate(R.layout.empty_view, parent, false))
                    R.layout.user_item_layout -> UserViewHolder(layoutInflater
                            .inflate(R.layout.user_item_layout, parent, false))
                    else -> throw IllegalArgumentException("Could not find view of type $viewType")
                }
            }
        }
        usersAdapter.setAreItemsClickable(true)
        user_list.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
        user_list.adapter = usersAdapter
        usersAdapter.setAllowSelection(true)
        intentStream = flowOf(intentStream, user_list.scrollEvents()
                .map { recyclerViewScrollEvent ->
                    GetPaginatedUsersIntent(
                            if (ScrollEventCalculator.isAtScrollEnd(recyclerViewScrollEvent))
                                viewState.lastId
                            else -1)
                }
                .filter { it.lastId != -1L }
                .conflate()
                .onEach { Log.d("NextPageIntent", "fired!") })
                .flattenMerge()
    }

    fun toggleLoadingViews(isLoading: Boolean, intent: UserListIntents?) {
        linear_layout_loader.bringToFront()
        linear_layout_loader.visibility = if (isLoading) View.VISIBLE else View.GONE
    }
    }

最佳答案

我认为这里的问题是intents.consumeEach正在等待intents关闭,但从未关闭过。这意味着resultFlows.consumeEach从未达到,因此getUsers似乎没有发出任何东西,但实际上并未被消耗。

快速解决方法是将每个consumeEach包装在launch中,但是我建议进行重构/重新设计。

旁注:返回Flow的函数不应暂停。

08-17 10:07