使用repeatOnLifecycle API进行流重新收集

huangapple go评论57阅读模式
英文:

Flow re-collecting using repeatOnLifecycle API

问题

当我在Activity/Fragment中使用repeatOnLifecycle收集StateFlow,然后导航到另一个Activity,再返回到基础Activity时,即使我不更新stateFlow,流也会重新收集。

例如:

在ViewModel中

private var _deletionStatusStateFlow = MutableStateFlow(0)
val deletionStatusStateFlow = _deletionStatusStateFlow.asStateFlow()

然后在Fragment中观察它:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){

         deleteAccountViewModel.deletionStatusStateFlow.collect {

           if (it == 1){
             startActivity(AnyActivity)
           }
         }
     }
}

每次我点击返回键时,它都会打开Activity

但是如果我在相同的示例中使用LiveData...观察块将不会再次执行(当Fragment返回到STARTED状态时)

如何实现与StateFlow类似的行为,就像LiveData一样?

对于简单的用法,有一个解决方案:当我使用flowWithLifecycle(...).distinctUntilChanged()

但这是复杂的:

val results: StateFlow<SearchResult> =
    queryFlow.mapLatest { query ->
        repository.search(query)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000L),
        initialValue = SearchResult.EMPTY
    )

上游将被重新生成(这将导致上游仓库读取成本增加)

英文:

When I collect a StateFlow in an Activity/Fragment using repeatOnLifecycle and then navigate to another activity then go back to the base one then the flow is re-collecting even if I don't update stateFlow.

For example:

in ViewModel

private var _deletionStatusStateFlow = MutableStateFlow(0)
val deletionStatusStateFlow = _deletionStatusStateFlow.asStateFlow()

then i observed it in Fragment:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){

         deleteAccountViewModel.deletionStatusStateFlow.collect {

           if (it == 1){
             startActivity(AnyActivity)
           }
         }
     }
}

it keeps open the activity every time I click onBackKey

but if i use LiveData with same example ... the observing block will not execute again (when coming back to the STATRED state in the Fragment -view- )

How do I achieve similar behavior to LiveData in StateFlow?

There's a solution for simple usage: it's when I use flowWithLifecycle(...).distinctUntilChanged()
but this is complex:

val results: StateFlow&lt;SearchResult&gt; =
    queryFlow.mapLatest { query -&gt;
        repository.search(query)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000L),
        initialValue = SearchResult.EMPTY
    )

the up stream will be re-generated (and that will cost an upstream repo read)

答案1

得分: 2

这是预期的行为,因为活动和片段可以多次重新创建。这就是为什么 repeatOnLifecycle 存在的原因。

您需要将数据包装在一个类中,该类还具有指示关联的导航事件是否发生的布尔属性。当Activity/Fragment中的收集器执行导航事件时,还应调用ViewModel函数,ViewModel使用该函数来更新Flow以发出一个事件,其中导航事件被视为已处理。这对于 stateIn 来说太复杂了。您需要使用 MutableStateFlow,以便根据Activity的反馈来更新值。

此过程在Android文档中描述在这里

英文:

This is expected behavior because Activities and Fragments can be recreated multiple times. That’s why repeatOnLifecycle exists in the first place.

You need to wrap your data in a class that also has a Boolean property indicating whether the associated navigation event has occurred. The collector in the Activity/Fragment, when it performs the navigation event, should also call a ViewModel function that the ViewModel uses to update the Flow to emit an event where the navigation event is considered handled. This is too complicated for stateIn. You’ll want to use a MutableStateFlow so you can update the values based on the feedback from the Activity.

This process is described in the Android documentation here.

答案2

得分: 0

导航通常最好由常规的 Flow(或 SharedFlow)来表示,而不是 StateFlow

导航事件应该只由收集器消耗一次,然后被丢弃,与可以随意重新读取的UI状态相对。如果不丢弃事件(Flow会为您处理此操作),则会出现您所看到的多次触发导航的行为。

考虑到这一点,我建议从您的视图模型中将导航暴露为一个 Flow,与您可能拥有的任何UI状态流分开。

视图模型

// 定义我们的UI状态流
private val _stateFlow = MutableStateFlow(initialState) // 顺便提一下:这个可变流不需要使用 "var",我们可以和应该使用 "val"
val stateFlow: StateFlow<UiState> = _stateFlow.asStateFlow()

// 定义我们的导航事件流
val navigationFlow: Flow<NavigationEvent> = ... // 构建导航流的逻辑在这里。
// 您可以根据需要使用 flow { ... } 或 MutableSharedFlow<NavigationEvent>,但只需要暴露一个 Flow。

如果您的视图模型有多种类型的导航事件,您可能会像这样明确定义它们。

模型

sealed interface NavigationEvent {
    object GoToXyzActivity : NavigationEvent
}

现在,我们将在 Activity 中收集我们从视图模型中暴露的流。

Activity

// 收集我们的UI状态
viewLifecycleOwner.lifecycleScope.launch {
    deleteAccountViewModel.stateFlow.collect {    
      
  viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // 在这里基于UI状态更新视图
        }
    }
}

// 收集我们的导航事件
viewLifecycleOwner.lifecycleScope.launch {
    // 如果您的导航与Activity的视图不交互,
    // 实际上不需要使用 `repeatOnLifecycle()`,因为 `startActivity()`
    // 不需要视图存在才能工作 - 我们可以在
    // Activity 的 `Lifecycle` 的任何阶段都可以工作,都可以正常工作。
    deleteAccountViewModel.navigationFlow.collect { navEvent ->
        when (navEvent) {
            GoToXyzActivity -> startActivity(XyzActivity)
            // 其他导航可以在这里添加
        }
    }
}

只是提一下,这是来自Kotlin关于StateFlows的文档的一部分:

与使用flow构建的冷流不同,StateFlow是热流:从流中进行收集不会触发任何生产者代码。

这意味着只要您将结果存储在视图模型中的 StateFlow 中,对存储库的上游调用不会重新触发。

英文:

Navigation is often best represented by a regular Flow (or a SharedFlow), rather than a StateFlow.
A navigation event should only be consumed once by collectors, and then discarded, as opposed to UI state which can be re-read as much as you like. If you do not discard the event (a Flow does this for you) you will get the behaviour you are seeing, where the navigation is triggered multiple times.

Considering this, I would expose navigation as a Flow from your view model, separate to any UI state flow you might have.

View Model

// Define our UI state flow
private val _stateFlow = MutableStateFlow(initialState) // Side note: this mutable flow does not need to use &quot;var&quot;, we can and should use &quot;val&quot;
val stateFlow: StateFlow&lt;UiState&gt; = _stateFlow.asStateFlow()

// Define our navigation event flow
val navigationFlow: Flow&lt;NavigationEvent&gt; = ... // Logic to construct navigation flow goes here.
// You might use flow { ... } or a MutableSharedFlow&lt;NavigationEvent&gt; depending on your needs, but you only need to expose a Flow.

If you have more than one type of navigation event for this view model, you'll want to have them clearly defined, like this.

Models

sealed interface NavigationEvent {
    object GoToXyzActivity : NavigationEvent
}

Now we'll collect the flows we've exposed from the view model in the Activity.

Activity

// Collect our UI state
viewLifecycleOwner.lifecycleScope.launch {
    deleteAccountViewModel.stateFlow.collect {    
      
  viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Update the view based on the UI state here
        }
    }
}

// Collect our navigation events
viewLifecycleOwner.lifecycleScope.launch {
    // If your navigation doesn&#39;t interact with the activity&#39;s view,
    // we actually don&#39;t need to use `repeatOnLifecycle()`, since `startActivity()`
    // does not required the view to exist to work - we could be in
    // any stage of the activity&#39;s `Lifecycle` and that&#39;s fine.
    deleteAccountViewModel.navigationFlow.collect { navEvent -&gt;
        when (navEvent) {
            GoToXyzActivity -&gt; startActivity(XyzActivity)
            // Other navigation can go here
        }
    }
}

Just an FYI, this is from the Kotlin docs on StateFlows

> Unlike a cold flow built using the flow builder, a StateFlow is hot: collecting from the flow doesn't trigger any producer code.

This means an upstream call to your repo won't be re-triggered as long as you're storing the result in a StateFlow in your view model somewhere.

答案3

得分: 0

尝试将生命周期从"started"更改为"created"

viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED)

改为以下代码

viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED)

前者告诉repeatOnLifecycle在生命周期至少处于"Started"状态时重复执行collect,这意味着每当生命周期处于"Started"状态时,它都会重新收集所述的流

repeatOnLifecycle(Lifecycle.State.STARTED)不同,后者仅在生命周期处于"CREATED"状态时重新收集流,这仅在片段创建时发生。

英文:

Try changing lifecycle from started to created

  viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED)

to this code

  viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED)

the former was telling repeatOnLifecycle to repeat collect when lifecycle atleast Started, which mean every time lifecycle on Started State it would re-collect of said flow

unlike repeatOnLifecycle(Lifecycle.State.STARTED), the later would only re-collect flow when lifecycle was on CRATED state, which is only happen on creation of fragment
CMIMW

huangapple
  • 本文由 发表于 2023年1月8日 23:50:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75049173.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定