Поток не излучающих предметов - PullRequest
1 голос
/ 29 октября 2019

Код ниже реализует простой экран, который показывает список пользователей, использующих шаблон MVI с сопрограммами kotlin. Код работает, но не дает никаких результатов. После многих отладок я обнаружил, что функция getUsers в UserListVM ничего не излучает. Был бы признателен за помощь здесь. Большое спасибо.

    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
    }
    }

1 Ответ

1 голос
/ 30 октября 2019

Я думаю, что проблема в том, что intents.consumeEach ожидает закрытия intents, но никогда не закрывается. Это означает, что resultFlows.consumeEach никогда не достигается, поэтому может показаться, что getUsers ничего не излучает, но на самом деле не потребляется.

Быстрое исправление - это обернуть каждый consumeEach в launch,но я рекомендую рефакторинг / редизайн.

Примечание: функции, которые возвращают Flow, не должны приостанавливаться.

...