为什么不调用嵌套片段 (ViewModel) 中流的收集?
Why is the collect of a flow in a nested fragment (ViewModel) not called?
我有一个设置,其中 Activity 包含两个片段(A、B),B 有一个包含 3 个片段(B1、B2、B3)的 ViewPager
在 activity (ViewModel) 中,我从 Room 观察模型 (Model
),并将结果发布到本地共享流。
val mSharedFlow = MutableSharedFlow<Model>` that I publish the model updates to:
...
viewModelScope.launch { repo.observeModel().collect(sharedFlow) }
片段 A 和 B(ViewModel)可以通过(父级)访问 sharedFlow fun getModelFlow(): Flow<Model>
没有问题 运行 每个片段的收集:
viewModelScope.launch {
parent.getModelFlow().collect { model -> doStuff(model) }
}
但是,问题出在嵌套片段中(B1 等)
在 B1 的片段 (ViewModel) 中,我有另一个 parent.getModelFlow()
依次调用片段 B (ViewModel) parent.getParentFlow()
我在获取流时没有问题(即 SharedFlow(来自 activity ViewModel 的流));但是 B1 中的 collect
什么都不做。
- 为什么我无法从嵌套的B1 中的共享流中收集? (当 A 和 B 工作正常时)
- 我是不是已经没有考虑到一些流程规则了?其他
launch{}'es
、其他withContexts(Some?)
、flowOn
、launchIn
等?
(提供流不是问题。即使我创建中间流,或将 sharedFlow 放在 kotlin Singleton 对象中,我仍然有同样的问题)
=== 编辑 ===
我被要求添加更多信息,不幸的是(对于所有人)我无法粘贴实际代码,因为它只会显得冗长和陌生(请参阅下面我的评论)。但是这里有一些应该等效的伪代码。
一个澄清,你可以在下面看到,Activity,FragmentA、FragmentB、FragmentB1等同时都是运行——但只有A和B之一可见一次。
class TheActivity {
fun onCreate() {
setupFragments()
}
/** Two fragments active at the same time,
but only one FrameLayout is visible at one time */
private fun setupFragments() {
val a = FragmentA.newInstance()
val b = FragmentB.newInstance()
supportFragmentManager.commit {
add(R.id.fragment_holder_a, a)
add(R.id.fragment_holder_b, b)
}
}
}
class ActivityViewModel {
val activityModelFlow = MutableSharedFlow<Model>()
fun start() {
viewModelScope.launch {
getRoomFlow(id).collect(activityModelFlow)
}
}
}
class FragmentA { // Not very interesting
val viewModel: ViewModelA
}
class ViewModelA {
fun start() {
viewModelScope.launch {
parentViewModel.activityModelFlow.collect { model ->
log("A model: $model")
}
}
}
}
class FragmentB {
val viewModel: ViewModelB
val viewPagerAdapter = object : PagesAdapter.Pages {
override val count: Int = 1
override fun title(position: Int): String = "B1"
override fun render(position: Int): Fragment = FragmentB1.newInstance()
}
}
class ViewModelB {
val bModelFlow: Flow<Model> get() = parentViewModel.activityModelFlow
fun start() {
viewModelScope.launch {
parentViewModel.activityModelFlow.collect { model ->
log("B model: $model")
}
}
}
}
class Fragment B1 {
val viewModel: ViewModelB1
}
class ViewModelB1 {
fun start() {
viewModelScope.launch {
// in essence:
// - ViewModelB.bModelFlow ->
// - ActivityViewModel.modelFlow
parentViewModel.bModelFlow.collect { model ->
log("B1 model: $model")
}
}
}
}
因此,获取 parentViewModels、DI、片段创建等所有连接都工作正常。但是 B1 model: $model
永远不会被调用!为什么?
这与片段、生命周期、流程和协程阻塞几乎没有关联(读作没有)——我认为这是背后的原因。
val activityModelFlow = MutableSharedFlow<Model>()
// is the same as
val activityModelFlow = MutableSharedFlow<Model>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
这意味着新订阅者将可以访问存储在重播缓存中的 0
(!) 个值。因此,当 FragmentB1 开始订阅时,模型已经发出。
解决方案(没有任何优化或进一步考虑)
private val bulletFlow = MutableSharedFlow<Bullet>(replay = 1)
(或使用 StateFlow,但我不想打扰初始 state/value)
我有一个设置,其中 Activity 包含两个片段(A、B),B 有一个包含 3 个片段(B1、B2、B3)的 ViewPager
在 activity (ViewModel) 中,我从 Room 观察模型 (Model
),并将结果发布到本地共享流。
val mSharedFlow = MutableSharedFlow<Model>` that I publish the model updates to:
...
viewModelScope.launch { repo.observeModel().collect(sharedFlow) }
片段 A 和 B(ViewModel)可以通过(父级)访问 sharedFlow fun getModelFlow(): Flow<Model>
没有问题 运行 每个片段的收集:
viewModelScope.launch {
parent.getModelFlow().collect { model -> doStuff(model) }
}
但是,问题出在嵌套片段中(B1 等)
在 B1 的片段 (ViewModel) 中,我有另一个 parent.getModelFlow()
依次调用片段 B (ViewModel) parent.getParentFlow()
我在获取流时没有问题(即 SharedFlow(来自 activity ViewModel 的流));但是 B1 中的 collect
什么都不做。
- 为什么我无法从嵌套的B1 中的共享流中收集? (当 A 和 B 工作正常时)
- 我是不是已经没有考虑到一些流程规则了?其他
launch{}'es
、其他withContexts(Some?)
、flowOn
、launchIn
等?
(提供流不是问题。即使我创建中间流,或将 sharedFlow 放在 kotlin Singleton 对象中,我仍然有同样的问题)
=== 编辑 ===
我被要求添加更多信息,不幸的是(对于所有人)我无法粘贴实际代码,因为它只会显得冗长和陌生(请参阅下面我的评论)。但是这里有一些应该等效的伪代码。
一个澄清,你可以在下面看到,Activity,FragmentA、FragmentB、FragmentB1等同时都是运行——但只有A和B之一可见一次。
class TheActivity {
fun onCreate() {
setupFragments()
}
/** Two fragments active at the same time,
but only one FrameLayout is visible at one time */
private fun setupFragments() {
val a = FragmentA.newInstance()
val b = FragmentB.newInstance()
supportFragmentManager.commit {
add(R.id.fragment_holder_a, a)
add(R.id.fragment_holder_b, b)
}
}
}
class ActivityViewModel {
val activityModelFlow = MutableSharedFlow<Model>()
fun start() {
viewModelScope.launch {
getRoomFlow(id).collect(activityModelFlow)
}
}
}
class FragmentA { // Not very interesting
val viewModel: ViewModelA
}
class ViewModelA {
fun start() {
viewModelScope.launch {
parentViewModel.activityModelFlow.collect { model ->
log("A model: $model")
}
}
}
}
class FragmentB {
val viewModel: ViewModelB
val viewPagerAdapter = object : PagesAdapter.Pages {
override val count: Int = 1
override fun title(position: Int): String = "B1"
override fun render(position: Int): Fragment = FragmentB1.newInstance()
}
}
class ViewModelB {
val bModelFlow: Flow<Model> get() = parentViewModel.activityModelFlow
fun start() {
viewModelScope.launch {
parentViewModel.activityModelFlow.collect { model ->
log("B model: $model")
}
}
}
}
class Fragment B1 {
val viewModel: ViewModelB1
}
class ViewModelB1 {
fun start() {
viewModelScope.launch {
// in essence:
// - ViewModelB.bModelFlow ->
// - ActivityViewModel.modelFlow
parentViewModel.bModelFlow.collect { model ->
log("B1 model: $model")
}
}
}
}
因此,获取 parentViewModels、DI、片段创建等所有连接都工作正常。但是 B1 model: $model
永远不会被调用!为什么?
这与片段、生命周期、流程和协程阻塞几乎没有关联(读作没有)——我认为这是背后的原因。
val activityModelFlow = MutableSharedFlow<Model>()
// is the same as
val activityModelFlow = MutableSharedFlow<Model>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
这意味着新订阅者将可以访问存储在重播缓存中的 0
(!) 个值。因此,当 FragmentB1 开始订阅时,模型已经发出。
解决方案(没有任何优化或进一步考虑)
private val bulletFlow = MutableSharedFlow<Bullet>(replay = 1)
(或使用 StateFlow,但我不想打扰初始 state/value)