g

    gitai

    1 year ago
    Given a user can press the home button at any given moment (in which case the app can get terminated / completely recomposed upon return) what is the use-case where you would choose to call 
    remember()
    over
    rememberSaveable()
    ?  and … is it really required to explicitly call
    remember()
    in such use-case? For example, here (see code in reply) the compiler will presumably add code to the
    Button
    composable content lambda to implicitly "remember" the captured
    MutableState<Boolean>
    instance referenced by
    hidden
    and restore it when the
    Button
    is clicked - that is when executing the content lambda again during recomposition. Since this lambda in the only composable to run upon such event I don't see why you would explicitly remember
    hidden
    at it’s declaration point, which kind of leads to a more basic question: Should we use
    remember()
    /
    rememberSaveable()
    "defensibly" to guard against recompositions at higher levels which may happen "out of the blue" ?
    class MainActivity : ComponentActivity() {	@SuppressLint("UnrememberedMutableState")
    @Composable
    fun Widget() {
    var hidden by mutableStateOf(true)
    Column() {
    Button(onClick = { hidden = !hidden }) {
    Text(text = if (hidden) "Show" else "Hide")
    }
    }
    }	override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    MyApplicationTheme {
    Widget()
    }
    }
    }
    }
    Albert Chang

    Albert Chang

    1 year ago
    Depending on an undefined behavior is always a bad idea. When and how often recomposition happens shouldn’t affect correctness of your program.
    g

    gitai

    1 year ago
    "Depending on an undefined behavior is always a bad idea" – That's a key discussion point. The compiler makes substantial effort to skip storing duplicate state passed between composable so I guess "remembering less" is a good thing, furthermore, not all types are easy to serialize/deserialize to Savables and I assume there are situations where one would actually want oblivion in order to have a UI experience that is consistent regardless of why the screen got recomposed. like, is it realy worth the effort to restart every kind of running animation at the exact frame it was stopped?
    Albert Chang

    Albert Chang

    1 year ago
    When and how often recomposition happens shouldn't affect correctness of your program.
    This is the principle. Restarting animation from where it stopped is not always the correctness, but keeping the
    hidden
    state obviously is.
    As for savesbles, it's the problem of
    rememberSaveable
    vs
    remember
    , not
    remember
    vs not remembering.
    g

    gitai

    1 year ago
    When and how often recomposition happens shouldn't affect correctness of your program.
    This is the principle. Restarting animation from where it stopped is not always the correctness, but keeping the hidden state obviously is.
    Well, I'm not exactly sure recomposition is or should be expected universal throughout the app...
    As for savesbles, it's the problem of rememberSaveable vs remember, not remember vs not remembering.
    It actually is, if you can't / don't use rememberSaveable() to store the state, for whatever reason, like type serialization difficulties then why call remember() at all - why make any explicit effort to "remember" anything
    ...that is throughout the app lifetime. I consider Android relaunching a terminated app as performing an "initial composition"
    t

    tad

    1 year ago
    remember
    is an optimization. If your state is complex to calculate on every recomposition (e.g. every frame) then
    remember
    allows you to memoize that state. Ideally, though, you'd hoist as much as possible (or as much as makes sense from an API standpoint) to avoid calculating that state in the first place.
    As I've gotten more comfortable with Compose, I've definitely found places where I used
    remember
    as a crutch to force idempotency, and that led to several subtle bugs and unnecessary recompositions. So it's tricky to get right for sure.
    g

    gitai

    1 year ago
    Thing is, that if you capture the state variable in a composable lambda the compiler will register that lambda for recomposition and implicitly remember it so this can also be thought of as a way to cache a result you want to avoid recalculating. I'm inclined to think you would use
    remember()
    in cases where your initial read is in the same scope where the state is created so you can have the scope itself register for recomposition in order to pass state updates to some transient widget which can only accept state through function arguments.
    Albert Chang

    Albert Chang

    1 year ago
    I don't think there's anything unclear. You are depending on the implementation detail of the compiler. You can do that but it's bad. That's all.https://kotlinlang.slack.com/archives/CJLTWPH7S/p1611243256027800?thread_ts=1611232921.023400&amp;cid=CJLTWPH7S
    g

    gitai

    1 year ago
    Ok. Thanks!
    Q: How can a composable that takes no state parameters be idempotent if it is susceptible to side effects, like click events in the Widget example above ? Cleary if I remember hidden at its declaration point then each recomposition of Widget would show a different button depending on its current state which is inconsistent with respect to calling Widget. In contrast if you don’t remember hidden you always show the same button on each call to Widget. So how would you write Widget to satisfy the idempotent principle in this case ?
    Albert Chang

    Albert Chang

    1 year ago
    What do you mean by "a different button" and "the same button"?
    g

    gitai

    1 year ago
    Different display of Button: [ Show ] vs. [ Hide ]
    Albert Chang

    Albert Chang

    1 year ago
    Then it's the opposite. If you remember
    hidden
    state, no matter how many times the function is called, the button will always have the remembered value. If you don't remember it, when
    hidden
    is false and the function is called again, it'll be reset to true.
    g

    gitai

    1 year ago
    If you remember hidden state, no matter how many times the function is called, the button will always have the remembered value
    Only if the Button was not clicked between calls to Widget.   If Widget may be recomposed at any point in time, then you have no definitive answer as to its result – it can display [ Show ] or [Hide] – you don’t know.
    If you don't remember it, when hidden is false and the function is called again, it'll be reset to true.
    Which is exactly what makes calls to Widget idempotent - it always displays the same button: [ Show ] regardless of any side effect.
    Albert Chang

    Albert Chang

    1 year ago
    Obviously you are misunderstanding "idempotent". Taking the definition from Wikipedia:
    Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.
    You can learn more about idempotence if you do some searching yourself. If you don't remember
    hidden
    , every call to the function will reset it to initial state, which itself is a side effect.
    g

    gitai

    1 year ago
    if you don't remember hidden, every call to the function will reset it to initial state, which itself is a side effect.
    I disagree. if I don't remember
    hidden
    I'm invoking a new instance of the function (Widget) - I'm not resetting anything.
    Albert Chang

    Albert Chang

    1 year ago
    That doesn't matter. What matters is that calling the function multiple times changes the result. Anyway I don't think this discussion is meaningful if you don't understand what is idempotent. You really need to do some searching.
    g

    gitai

    1 year ago
    Calling Widget() multiple times when it is coded to not remember hidden will always produce the same result - its a fact.
    Albert Chang

    Albert Chang

    1 year ago
    It's a fact but it has nothing to do with whether the function is idempotent.
    g

    gitai

    1 year ago
    I think its all about being pragmatic.   Here is some additional reading:   “Idempotent, in computer science, the term is used more comprehensively to describe an operation that will produce the same results if executed once or multiple times.”   “This may have a different meaning depending on the context in which it is applied.”   “Simply put, an operation is Idempotent if it produces the same result when called over and over”  https://ldapwiki.com/wiki/Idempotent
    Albert Chang

    Albert Chang

    1 year ago
    Idempotent means given a specific state, without external input, the operation will always produce the same results. So what matters is not whether the result is the same as the initial result, but whether it changes when executed once vs multiple times.
    g

    gitai

    1 year ago
    Does this imply that an Idempotent function can return a new result in response to some external input that's different from it’s initial result and still be considered "Idempotent" so long as it continues to return that new result ?
    Albert Chang

    Albert Chang

    1 year ago
    Yes.
    g

    gitai

    1 year ago
    In that case you're right. Widget without remembering hidden is not an Idempotent function.
    btw, are you familiar with a tool / flag that can be set to trace recomposition events per function? I'm currently using plain log.i() but that quickly litters the code ...
    Albert Chang

    Albert Chang

    1 year ago
    You mean not adding any code? That's definitely not possible.
    g

    gitai

    1 year ago
    I mean maybe the framework has some flag you can set in code or a build option to have the runtime output such events to stdout; it's common to have for debugging purposes.
    Albert Chang

    Albert Chang

    1 year ago
    No AFAIK.