Klaas Kabini

    Klaas Kabini

    2 years ago
    @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.
    @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?
    @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.
    @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]

    2 years ago
    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:
    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
    pula

    pula

    2 years ago
    is Modifier considered in this “skippability”?
    l

    Leland Richardson [G]

    2 years ago
    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
    Klaas Kabini

    Klaas Kabini

    2 years ago
    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]

    2 years ago
    @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!