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

Fix new presenter instances of the same type not being recomposed #799

Merged
merged 4 commits into from
Aug 14, 2023

Conversation

ZacSweers
Copy link
Collaborator

This is an interesting edge case I ran into and subsequently learned about after talking with other folks. In short, because compose treats these by type and source position, you can think of the following two snippets as equivalent.

class Foo(val value: String) {
  @Composable
  fun Content() {
    val myState = remember { init(value) }
  }
}
@Composable
fun Content(self: Foo) {
  val myState = remember { init(self.value) }
}

This affects Circuit presenters disproportionately because their present() calls have no other inputs to disambiguate. As a result, compose skips this in cases where you have a possibly different screen but same presenter type, and we need to explicitly force recomposition with a key().

A good example of this issue can be seen like so

var count by remember { mutableIntStateOf(0) }
val screen = CountScreen(count)

Column {
  Button(onClick = { count++ }) {
    Text("Increment count")
  }
  CircuitContent(screen)
}

Where ExampleScreen just displays the current count. Before this change, this would never recompose the state.

This is an interesting edge case I ran into and subsequently learned about after talking with other folks. In short, because compose treats these by type and source position, you can think of the following two snippets as equivalent.

```kotlin
class Foo(val value: String) {
  @composable
  fun Content() {
    val myState = remember { init(value) }
  }
}
```

```kotlin
@composable
fun Content(self: Foo) {
  val myState = remember { init(self.value) }
}
```

This affects Circuit presenters disproportionately because their present() calls have no other inputs to disambiguate. As a result, compose skips this in cases where you have a possibly different screen but same presenter _type_, and we need to explicitly force recomposition with a `key()`.

A good example of this issue can be seen like so

```kotlin
var count by remember { mutableIntStateOf(0) }
val screen = CountScreen(id)

Column {
  Button(onClick = { id++ }) {
    Text("Increment count")
  }
  CircuitContent(screen)
}
```

Where `ExampleScreen` just displays the current count. Before this change, this would never recompose the state.
@ZacSweers ZacSweers requested a review from kierse August 14, 2023 03:16
@ZacSweers
Copy link
Collaborator Author

Depends on #800


// Regression test for https://github.com/slackhq/circuit/pull/799
@Test
fun contentShouldRecomposeStateWhenPresenterInstanceChanges() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed this fails with the previous behavior and passes now 🎉

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Collaborator

@kierse kierse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. It makes sense to me - why would Compose rerun the zero argument present() method? From it's perspective, nothing has changed


// Regression test for https://github.com/slackhq/circuit/pull/799
@Test
fun contentShouldRecomposeStateWhenPresenterInstanceChanges() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@ZacSweers
Copy link
Collaborator Author

Interesting. It makes sense to me - why would Compose rerun the zero argument present() method? From it's perspective, nothing has changed

The surprising thing is that it's not exactly zero argument, the enclosing class becomes an implicit receiver parameter and in theory that has changed, but compose doesn't consider it that way unless the type is different.

@ZacSweers ZacSweers added this pull request to the merge queue Aug 14, 2023
Merged via the queue into main with commit 72ce311 Aug 14, 2023
1 check passed
@ZacSweers ZacSweers deleted the z/fixState branch August 14, 2023 22:08
chrisbanes added a commit to chrisbanes/circuit that referenced this pull request Sep 19, 2023
The current implementation uses the presenter itself
as the key(), which does fix the original issue. For our
usage though, the presenter will always be a new instance,
and therefore there will never be a consistent key across
back stack changes (i.e. a push, then pop). This then forces
the currentCompositeKeyHash for the underlying content
underneath to change, which then breaks rememberRetained() when
used without an explicit key.

Fixed by changing the key to the `screen` instance instead, which
achieves the same original fix, but the screen is consistent across
navigation changes, which allows the currentCompositeKeyHash to be
consistent.
chrisbanes added a commit to chrisbanes/circuit that referenced this pull request Sep 19, 2023
The current implementation uses the presenter itself
as the key(), which does fix the original issue. For our
usage though, the presenter will always be a new instance,
and therefore there will never be a consistent key across
back stack changes (i.e. a push, then pop). This then forces
the currentCompositeKeyHash for the underlying content
underneath to change, which then breaks rememberRetained() when
used without an explicit key.

Fixed by changing the key to the `screen` instance instead, which
achieves the same original fix, but the screen is consistent across
navigation changes, which allows the currentCompositeKeyHash to be
consistent.
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

Successfully merging this pull request may close these issues.

2 participants