简体   繁体   English

发出 Paging3 流后无法收集任何流

[英]Can not collect any flow after emitting Paging3 flow

I am using paging3 and I have two different paging source.我正在使用paging3 ,并且有两个不同的分页源。 The problem is Coroutine Scope only emit first paging flow问题是Coroutine Scope只发出第一个寻呼流

In ViewModel I have two paging flowViewModel我有两个分页流程

val pagingFlow1 = Pager(PagingConfig(pageSize = 50, prefetchDistance = 1)) {
    pagingSource
}.flow.cachedIn(viewModelScope)

val pagingFlow2 = Pager(PagingConfig(pageSize = 50, prefetchDistance = 1)) {
    pagingSource2
}.flow.cachedIn(viewModelScope)

Collect them in activity在活动中收集它们

    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.pagingFlow1.collectLatest { pagingData ->
            pagingAdapter.submitData(pagingData)
        }
        viewModel.pagingFlow2.collectLatest { pagingData ->
            pagingAdapter2.submitData(pagingData)
        }
    }

But lifecycleScope only emit pagingFlow1 in other words paging works only first recyclerView.但是lifecycleScope只发出pagingFlow1换句话说分页只在第一个 recyclerView 工作。

When I change the order this time only works for pagingFlow2当我这次更改顺序时仅适用于pagingFlow2

    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.pagingFlow2.collectLatest { pagingData ->
            pagingAdapter.submitData(pagingData)
        }
        viewModel.pagingFlow1.collectLatest { pagingData ->
            pagingAdapter2.submitData(pagingData)
        }
    }

In order to make sure I tested it with basic flows and works normally为了确保我用基本流程测试它并正常工作

// Flows in ViewModel
val testFlow1 = flowOf(1,2,3)
val testFlow2 = flowOf(4,5,6)

// Activity
    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.testFlow1.collectLatest { item ->
            Log.d(item)
        }
        viewModel.testFlow2.collectLatest { item ->
            Log.d(item)
        }
    }

I can not figure out why only first flow emitted while using Paging?我不明白为什么在使用分页时只发出第一个流? Anyone give me a clue?有人给我线索吗?

While trying different things, I found some interesting behaviour.在尝试不同的事情时,我发现了一些有趣的行为。 We can not collect anything if first collect pagingFlow如果先收集 pagingFlow,我们将无法收集任何东西

    val flow3 = flowOf(1,2,3)
    lifecycleScope.launch(Dispatchers.IO) {
        flow3.collectLatest { pagingData ->
            LogUtils.d("PagingFlow3 $pagingData")
        }
        viewModel.pagingFlow1.collectLatest { pagingData ->
            LogUtils.d("PagingFlow1 $pagingData")
            pagingAdapter.submitData(pagingData)
        }
        viewModel.pagingFlow2.collectLatest { pagingData ->
            LogUtils.d("PagingFlow2 $pagingData")
            pagingAdapter2.submitData(pagingData)
        }
    }

First flow3 collected than pagingFlow1 collected but pagingFlow2 not collected第一个flow3收集比pagingFlow1收集但pagingFlow2收集

If we put flow3 below pagingFlow1 it will not collected如果我们将 flow3 放在pagingFlow1下面,它就不会被收集

    val flow3 = flowOf(1,2,3)
    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.pagingFlow1.collectLatest { pagingData ->
            LogUtils.d("PagingFlow1 $pagingData")
            pagingAdapter.submitData(pagingData)
        }
        flow3.collectLatest { pagingData ->
            LogUtils.d("PagingFlow3 $pagingData")
        }
        viewModel.pagingFlow2.collectLatest { pagingData ->
            LogUtils.d("PagingFlow2 $pagingData")
            pagingAdapter2.submitData(pagingData)
        }
    }

Only pagingFlow1 collected仅收集pagingFlow1

collectLatest suspends until the flow finishes, so you need to launch separate jobs. collectLatest 暂停直到流程完成,因此您需要启动单独的作业。

Also, you don't need to dispatch on IO dispatcher.此外,您不需要在 IO 调度员上进行调度。

EDIT: Some changes to Paging since this answer was posted - it no longer matters what Dispatcher you call .submitData on.编辑:自发布此答案以来对 Paging 进行了一些更改 - 您调用.submitData的 Dispatcher 不再重要。 The only thing it influences is maybe where allocations on init happen, and maybe you want to start those from a non-ui thread, but in general it will have no impact on performance.它唯一影响的可能是 init 的分配发生的位置,并且您可能想从非 ui 线程启动这些分配,但通常它不会对性能产生影响。

eg,例如,

lifecycleScope.launch {
    viewModel.pagingFlow1.collectLatest { pagingData -> 
        pagingAdapter.submitData(pagingData) } }

lifecycleScope.launch {
    viewModel.pagingFlow2.collectLatest { pagingData -> 
        pagingAdapter2.submitData(pagingData) } }
  1. First you have to realize that you are retrieving value asynchronously whenever you use flows in your code.首先,您必须意识到,无论何时在代码中使用流,您都在异步检索值。 This means that the.collect call on your activity will have to wait until a value is emitted from the flow.这意味着对您的活动的 the.collect 调用必须等到从流中发出一个值。 I am assuming this only happens when you read from a database in your case, which if you are using flows in your DAO return type, will only be triggered if some function changes a database field.我假设这只会在您从您的案例中读取数据库时发生,如果您在 DAO 返回类型中使用流,则只有在某些 function 更改数据库字段时才会触发。 This translates to, until someone changes the database do not go pass this point in the code (In other words it blocks the thread).这意味着,在有人更改数据库之前,不要 go 在代码中传递这一点(换句话说,它会阻塞线程)。 The fix to this is running both of your.collect parallel instead of sequentially.对此的修复是同时运行 your.collect 并行而不是顺序运行。 This is showcased in dlam's answer where he/she runs them parallel and they work.这在 dlam 的回答中得到了展示,他/她并行运行它们并且它们可以工作。

  2. Okay so his/her answer works, so why do we need this post then?好的,他/她的回答有效,那我们为什么需要这篇文章呢? Well, although it works for the user, all these operations are running on the Main thread which basically freezes the UI for a certain amount of frames since the thread that is responsible for updating the UI(Dispatchers.Main) is busy doing your operations.嗯,虽然它适用于用户,但所有这些操作都在主线程上运行,这基本上会冻结 UI 一定数量的帧,因为负责更新 UI 的线程 (Dispatchers.Main) 正忙于执行您的操作。 This can be seen on your logcat with the message:这可以在您的 logcat 上看到以下消息:

I/Choreographer: Skipped xAmountOf frames.我/编舞:跳过了 xAmountOf 帧。 The application may be doing too much work on its main thread.应用程序可能在其主线程上做了太多工作。

To avoid freezing the UI the Main thread should only be used for updating the UI.为避免冻结 UI,主线程应仅用于更新 UI。 The fix for this is to run these operations on a background thread using the Dispatchers.IO解决方法是使用 Dispatchers.IO 在后台线程上运行这些操作

  1. If you get up to #2 you will probably get an error:如果你到达#2,你可能会得到一个错误:

java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered java.lang.IllegalArgumentException:已注册具有给定密钥的 SavedStateProvider

This is caused as a result of you calling.cachedIn(ViewmodelScope) on your flows.这是由于您在流程上调用 .cachedIn(ViewmodelScope) 造成的。 This method caches in the paged data on your scope so you can access it faster the next time you need it and it gets garbage collected when the scope you passed in is terminated, the error basically says you tried to save a second set of data with the same key as a set which is already stored.此方法缓存在您的 scope 上的分页数据中,因此您可以在下次需要时更快地访问它,并且当您传入的 scope 终止时,它会被垃圾收集,该错误基本上表示您试图保存第二组数据与已存储的集合相同的密钥。

  1. I am assuming these SavedStateProvider keys are probably autogenerated.我假设这些 SavedStateProvider 键可能是自动生成的。 This means that whichever.collect gets triggered first (remember that they are now running parallel so it could be either one) will save with an autogenerated key, lets say for simplicity purposes "1" which is fine, however when the second.collect gets triggered and it goes to save its SavedStateProvider it for some reason also autogenerates "1" as the key which cause the conflict.这意味着 whichever.collect 首先被触发(记住它们现在并行运行,所以它可以是任何一个)将使用自动生成的密钥保存,为了简单起见,可以说“1”很好,但是当 second.collect 得到触发并保存其 SavedStateProvider 由于某种原因它还会自动生成“1”作为导致冲突的键。 The key to finding the solution to this issue is to find out what causes them to autogenerate the same key.找到解决此问题的关键是找出导致它们自动生成相同密钥的原因。

  2. I think that the reason they are generating the same key is because they are running on parallel threads and the second to be triggered generates the key before the first has finished saving it, therefore the second does not know that there is already something with that key being cached.我认为他们生成相同密钥的原因是因为它们在并行线程上运行,第二个被触发的密钥在第一个完成保存之前生成密钥,因此第二个不知道该密钥已经存在被缓存。 This is supported by the fact that on dlam's answer this error did not come up, why not?在 dlam 的回答中没有出现这个错误这一事实支持了这一点,为什么不呢? most likely something to do with Main dispatcher running them instead of the IO dispatcher.最有可能与运行它们的主调度程序有关,而不是 IO 调度程序。

  3. At this point I would say the fix is to make sure that the key is only generated once, but since we do not have access to the code, we cannot do much in that regard... I'd say your best bet is to remove the.cachedIn operator from your flows, then presumably this will work.在这一点上我会说修复是为了确保密钥只生成一次,但由于我们无权访问代码,我们在这方面不能做太多......我会说你最好的选择是从您的流程中删除 the.cachedIn 运算符,然后大概这会起作用。

     lifecycleScope.launch(Dispatchers.IO) { viewModel.pagingFlow1.collectLatest { pagingData -> LogUtils.d("PagingFlow1 $pagingData") pagingAdapter.submitData(pagingData) } } lifecycleScope.launch(Dispatchers.IO) { viewModel.pagingFlow2.collectLatest { pagingData -> LogUtils.d("PagingFlow2 $pagingData") pagingAdapter2.submitData(pagingData) } }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM