【问题标题】:Flow not emitting items流不发射项目
【发布时间】:2020-02-24 21:51:34
【问题描述】:

下面的代码实现了一个简单的屏幕,显示了一个使用 MVI 模式和 kotlin 协程的用户列表。代码运行但不产生任何结果。经过大量调试,我发现 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
    }
    }

【问题讨论】:

  • 它是否正确调用?已经基本使用了协程,但是您不应该正确调用 scope.launch { getUsers() } 暂停的乐趣吗?
  • 我仍在尝试围绕流程和渠道进行思考。那么你建议如何正确调用它?
  • 暂停函数 'callGetApi' 只能从协程或其他暂停函数中调用 我的调用是错误的。也许尝试改变返回流 { emit(UsersResult(listOf(User("user1", 1), User("user2", 2), User("user3", 3)))) }.flowOn(Dispatchers.IO) with return flowOf(UsersResult(listOf(User("user1", 1), User("user2", 2), User("user3", 3))).flowOn(Dispatchers.IO)
  • 我认为这是因为您没有在 getUsers() 上调用终端操作员,因为流是冷可观察的。
  • @MilanKundacina 我采纳了你的建议,但仍然没有

标签: android kotlin kotlin-coroutines


【解决方案1】:

我认为这里的问题是 intents.consumeEach 正在等待 intents 关闭,但它永远不会关闭。这意味着永远不会到达resultFlows.consumeEach,所以看起来getUsers 没有发出任何东西,但实际上并没有被消耗。

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

旁注:返回 Flow 的函数不应挂起。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    相关资源
    最近更新 更多