Given a user can press the home button at any give...
# compose
g
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()
}
}
}
}
a
Depending on an undefined behavior is always a bad idea. When and how often recomposition happens shouldn’t affect correctness of your program.
g
"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?
a
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
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
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
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.
a
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
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 ?
a
What do you mean by "a different button" and "the same button"?
g
Different display of Button: [ Show ] vs. [ Hide ]
a
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
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.
a
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
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.
a
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
Calling Widget() multiple times when it is coded to not remember hidden will always produce the same result - its a fact.
a
It's a fact but it has nothing to do with whether the function is idempotent.
g
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
a
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
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 ?
a
Yes.
g
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 ...
a
You mean not adding any code? That's definitely not possible.
g
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.
a
No AFAIK.