Skip to content

Discuss whenStarted function deprecation #705

@denis-bezrukov

Description

@denis-bezrukov

@gpeal @elihart @sav007
In Lifecycle 2.6.0 the functions whenXXX/launchWhenXXX were deprecated, see explanation here

Overall they outline four options for the work to behave when the lifecycle fall below "X" state:

  1. The work is paused (current, but deprecated behavior of whenStarted)
  2. Let the suspending code continue to run to completion (even if state falls below target state)
  3. Cancel the suspending code and restart it when you come back above that state.
  4. Cancel the suspending code and don't restart it

Initially, maverick's viewModel.onEach behaved as 2 - the callback was guaranteed to be triggered when lifecycle state is above state, but execution wasn't paused/cancelled if the state falls below started state.
e.g. the following was possible:

viewModel.onEach(State::property) { property -> 
    binding.textView.text = "A" // guaranteed state above Started
    delay(1000)
    binding.textView.text = "B" // no guarantee

In #665 the behavior was changed to 1, as the callback is wrapped into whenStarted

As it is deprecated now (in #689 deprecations were suppressed), eventually it might be removed/hidden so we need to figure out what would be the best expected behavior for mavericks.

Here are my thoughts

  1. Behavior 3 & 4 do not make much sense as on higher level there is a DeliveryMode (UniqueOnly, RedeliverOnStart) which allow to configure similar behavior - RedeliveryOnStart=Behavior 3, UniqueOnly=Behavior 4
  2. Behavior 1 (current, with whenStarted) only makes sense with UniqueOnly, as with RedeliverOnStart previous work will not be resumed (so it will be equivalent to Behavior 4). It will be cancelled because of new 'redeliver' emission
  3. Behavior 2 (behavior prior 3.0.2) actually still makes sense. I was thinking some more about the example in Wrap subscription action with lifecycle whenStarted #665
override fun onCreate(savedInstanceState: Bundle?) {
  viewModel.onEach(ViewState::isVisible) { isVisible ->
    ...
    binding.someview.reveal()  // (1) suspendable, performs animation
    binding.someview.text = "" // (2) touches other UI after continuation
    ...
  }
}

Despite it fixes crash (if view's lifecycle fall into DESTROYED state, while fragment's lifecycle is still in CREATED), it introduce some potential issues.

  • Issue 1 - the code won't be ever "resumed" from suspended state just because of RedeliverOnStart, so binding.someview.text = "" will never be called

  • Issue 2 - even if it would resume (e.g. if UniqueOnly delivery mode was used), binding.someview.text = "" might reference to another view (if view was recreated).

I think the proper solution/advice would be to move viewModel.onEach that does "suspending stuff with a view" to Fragment.onViewCreated. In this case the job will be created in view's lifecycle scope, so the animation will be tied to view rather than fragment which is more correct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions