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

Klaas Kabini

02/20/2020, 7:12 PM
@Leland Richardson [G] Can you confirm if I understood the content of your presentation very well (

https://www.youtube.com/watch?v=6BRlI5zfCCk

) . May you point out any knowledge gaps in my understanding and help me in understand this better. The
@Model
annotation will make the count property inside the
LabelState
data class observable which in turn gives it the ability to trigger recompositions on subsequent mutations.
Copy code
@Model
data class LabelState(var count:Int)
Sample 1: Composable functions are positionally memoized meaning the inputs to composable functions, and their corresponding outcome resulting from composable functions calls are cached to avoid unneccessary recompositions when a composable function is called with the same inputs multiple times. In case where a composable function is called with the same inputs multiple times, compose runtime will check against the Slot table whether the same inputs have corresponding outputs that were previously cached. If yes, then the compose runtime will retrieve the previously cached results otherwise insert new inputs and their corresponding results to the Slot Table. Given that understanding, it means that the
LabelState
passed as input parameter to a composable in the following code snippet will be positionally memoized despite that is is not wrapped inside
remember
composable. Did I understood this correctly? If yes, then is this only applicable to situations where the state is hoisted? - in other words passed down the composable hierarchy from the owner to called composable functions?
Copy code
@Composable
fun LabelThatPassStateAsParameter(labelState: LabelState){
    Text(text = "$labelState.count",
         style = MaterialTheme.typography().body1)
}
Sample 2 Alternatively, when the state is not passed as input to composable function, it can be obtained by a keeping a local variable that references the state by calling composable function such as
state
or a combination of
@Model
class wrapped inside
remember
composable as in code snippet below.
val labelState = state {0}
is equivalent to
val labelState = remember { mutableStateOf(0) }
where both does referential equality between old and new value to prior to mutation of state.
Copy code
@Composable
fun LabelWithStateComposableThatDoesReferentialEquality(){
   val labelState = state {0}    
   Text(text = "$labelState.count", 
        style = MaterialTheme.typography().body1)
}
Otherwise if you want to do structural equality on state mutations, you can possible achieve by through the following. val
labelState2 = remember { LabelState(0) }
is equivalent to
val labelState2 = remember { mutableStateOf(value = 0, areEquivalent = StructurallyEqual) }
where both does structural equality between old and new value to prior to mutation of old state value.
l

Leland Richardson [G]

02/20/2020, 7:38 PM
hey! great questions. i’m not sure if i understand exactly what you’re asking, but let me explain a few things and let me know if that answers your question.
In case where a composable function is called with the same inputs multiple times, compose runtime will check against the Slot table whether the same inputs have corresponding outputs that were previously cached. If yes, then the compose runtime will retrieve the previously cached results otherwise insert new inputs and their corresponding results to the Slot Table.
This is more or less correct, but there are some nuances. We call this “skipping” in that we will “skip” the execution of a composable function if certain criteria is met where we think it is safe to do so. That criteria is something we are constantly evaluating and may shift over time as our compiler gets smarter or we change implementations. Right now, one big limitation here is that we are only skipping when all of the parameters you passed into the composable function are either @Model, @Immutable, or a “primitive” type. We may relax this constraint in the future, but we would rather start in a conservative place since “false positives” here can significantly reduce users trust in the system. Another side effect of this is that currently lambda literals are almost guaranteed to break the “skippability” of a composable, however we are working on a very significant optimization that will allow us to treat lambdas differently.
Given that understanding, it means that the
LabelState
passed as input parameter to a composable in the following code snippet will be positionally memoized despite that is is not wrapped inside
remember
composable.
I’m not sure what you mean by this, but let’s use an example… If i write the code:
Copy code
LabelThatPassStateAsParameter(LabelState(0))
Each time this code runs, the
LabelThatPassStateAsParameter
composable will also run because I am creating a new LabelState each time. The function will compare the new instance with the old instance and find that they are different, so it will re-execute it
p

pula

02/20/2020, 7:48 PM
is Modifier considered in this “skippability”?
l

Leland Richardson [G]

02/20/2020, 8:11 PM
that’s a good question. it is (and i was avoiding talking about that because it has a more complicated answer). Part of the API contract of modifiers is that they are publicly immutable, so compose compares them and skips if they are the same
@Klaas Kabini i was just looking at your code and saw that LabelState was a data class. That changes things a little bit. It should still “work” but your skipping might not happen as you expect because it is observing the @Model in the parent scope because the
equals
method that the data class generated caused the parent scope to get subscribed (since it was a read)
we have considered making @Model on data classes a warning or even an error
it’s usually not what you want
similarly, data classes with any
var
properties is arguably an anti-pattern
k

Klaas Kabini

02/21/2020, 6:08 AM
Thank you @Leland Richardson [G], I am still learning the whole paradigm of declarative UI pattern and reactive. I have being doing imperative style programming for a decade. It takes a bit of mental model shift to have everything nailed down to the right patterns. I keep on repetitively watching your presentation and digging into androidx-master-dev sources to learn things. My goal is to understand all nitty gritty internals of compose so that I can start sharing some of the cool stuff with other developers at GDG Johannesburg and Pretoria. Besides, jetpack compose along with coroutines are stuff that I am deeply excited about.
The fact that you guys are developing compose in the open is huge benefit to us developers with regards to learning advanced concepts and internals very early
l

Leland Richardson [G]

02/21/2020, 6:50 AM
@Klaas Kabini that’s very cool to hear. It’s really nice to see people dig into the code. I am more than happy to answer any questions you have or any concepts you’d like some more clarifications on!
10 Views