Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State production suspension and caching with Molecule #271

Closed
tunjid opened this issue Jul 20, 2023 · 2 comments
Closed

State production suspension and caching with Molecule #271

tunjid opened this issue Jul 20, 2023 · 2 comments

Comments

@tunjid
Copy link

tunjid commented Jul 20, 2023

There are two questions in this:

  1. suspending a launched Molecule: CoroutineScope.launchMolecule implies in name and verifies in source that the launched Molecule will keep producing state while the launching Coroutine is active. In the case where there is no collector of the produced state, can there be a way to stop composing the Presenter?
  2. If the above can be accommodated, can there be an API to seed the last produced state to presenters? This is so that resuming collecting from the presenter does not overwrite the last produced state value.

Consider the ProfilePresenter example:

@Composable
fun ProfilePresenter(
  userFlow: Flow<User>,
  balanceFlow: Flow<Long>,
): ProfileModel {
  val user by userFlow.collectAsState(null)
  val balance by balanceFlow.collectAsState(0L)

  return if (user == null) {
    Loading
  } else {
    Data(user.name, balance)
  }
}

The initial state is produced will always be Loading. In the case where Data has been produced and the ProfilePresenter is no longer being composed because the StateFlow is not being collected from, (maybe the owning screen has been placed in the back stack), I would like to prevent the last seen Data from immediately being overwritten by Loading as I return to the screen and the presenter produces state yet again.

i.e:

  1. Screen in focus. State: Loading.
  2. Data emitted. State: Data.
  3. Screen in back stack, presenter is no longer composing. State: Data.
  4. Screen back in focus, presenter is recomposed. Last seen Data state is overwritten. State: Loading.

The above can be worked around by using moleculeFlow with the Flow builder to allow seeding as the Flow is cold, and then creating a StateFlow with the right SharingStarted argument:

class SeededStateHolder {
    var seed: ProfileModel = Loading
    val profileStateFlow = flow {
        // Pass the seed to the presenter to prevent overwriting state
        emitAll(moleculeFlow(ProfilePresenter(seed, ....))
    }
    // update the seed on each emission
    .onEach { seed = it }
    .stateIn(...)
}

Though I wonder if I'm missing something more obvious. If not, a built in API that allows for it would be really nice.

@jingibus
Copy link
Contributor

In the case where there is no collector of the produced state, can there be a way to stop composing the Presenter?

The StateFlow created by launchMolecule is fed by a coroutine residing in the calling CoroutineScope. This coroutine runs the composition. If you kill that coroutine (e.g. by cancelling the Job in that CoroutineScope), it will stop composing. But there is no way to restart composition in that case.

If the above can be accommodated, can there be an API to seed the last produced state to presenters? This is so that resuming collecting from the presenter does not overwrite the last produced state value.

If you want to stop composition and restart it, that means managing the StateFlow by hand instead of using Molecule's API. The approach you wrote is how you'd do that: use moleculeFlow to create a cold flow that does its own composition, and then use stateIn to share it into a StateFlow. stateIn provides various options for starting/stopping the driving coroutine depending on how many collectors it has.

@tunjid
Copy link
Author

tunjid commented Jul 21, 2023

Gotcha, if I understand correctly:

  • launchMolecule is not cancelable and resumable by design. It mirrors CoroutineScope.launch in this regard.
  • For use cases where being able to stop and resume state production is desired (e.g Android lifecycle and navigation changes), users should opt for moleculeFlow and add their restart semantics on top of it.

I'll close the issue, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants