https://kotlinlang.org logo
#compose
Title
# compose
g

Garret Yoder

02/29/2024, 8:10 PM
I'm seeing some interesting behavior. I have an object that gets created & it has a composable function as part of it. It has a few states that are inside of it for the composable function. I keep an instance of this object as a state. When I make a new instance and replace the state, the states inside the object are preserved from the last instance. Has anyone experienced this before? My understanding is this is a whole new instance so the child states should all be destroyed. Compose definitely has the new object since any immutable non-state data is updated. Setting it to null first, then re-assigning it seems to clear the child states too.
z

Zach Klippenstein (he/him) [MOD]

02/29/2024, 8:23 PM
can you share some code?
g

Garret Yoder

02/29/2024, 8:24 PM
Let me see if I can get it to reproduce in a few lines of code. This is a "rip my UI apart" kind of code. One sec
z

Zach Klippenstein (he/him) [MOD]

02/29/2024, 8:25 PM
if you create the states and remember them inside the composable method, then they will be preserved even if you change the object. Creating a composable method on a class is the same, as far as compose is concerned, as creating a top-level composable and passing the object as a parameter. The lifetime of anything in that composable is tied to the composition, not the receiver object. The “identity” of the method is the same even if you change the receiver because as far as compose is concerned, you’re just calling the same function but passing in a different receiver parameter
g

Garret Yoder

02/29/2024, 8:26 PM
Ohh okay, so a compose function's data isn't actually a member of the object the function was a part of?
c

Casey Brooks

02/29/2024, 8:27 PM
This behavior seems correct to me. Compose tracks identity by the contents of a Composable function, not really by the function reference itself. So when a function is called by the 2 instances of the same class, it will still have all the same “function identifiers” that the Compose Compiler generated in the composition between both instances, and thus any
remember { }
blocks will be remembered when you swap the instance. When you set it to null, you remove the corresponding block of code from the composition, and then add it back later, which is why it clears the remembered state values
g

Garret Yoder

02/29/2024, 8:28 PM
Huh. How does that then work in a list, where it'd have to keep track of which objects data it started with, or is it just acting like arguments when the widget is initially created?
z

Zach Klippenstein (he/him) [MOD]

02/29/2024, 8:28 PM
Copy code
class Foo {
  private var bar by mutableStateOf(…)

  @Composable fun foo() {
    var baz by remember { mutableStateOf(…) }
  }
}
Here
bar
is owned by the object, and if you pass a different object, the composable will see a new object for
bar
.
baz
OTOH is owned by the composition, and will stay the same object even if a different instance of
Foo
Effectively
foo
is
Copy code
@Composable fun foo(receiver: Foo) {
  var baz by remember { mutableStateOf(…) }
}
Lists typically should use the
key
function to keep composable state associated with their list item when list items move around
c

Casey Brooks

02/29/2024, 8:29 PM
This is the “magic” that the Compose Compiler does. There’s a really good writeup of the theory behind it that came out when Compose hit 1.0, let me see if I can find it for you. It might be helpful for understanding why it’s behaving in this way
g

Garret Yoder

02/29/2024, 8:30 PM
Huh, I learned something new today. Lifting the state out of the compose function and into the class itself fixed it, thanks!