Felix
05/28/2024, 4:41 PM@Composable
function?zt
05/28/2024, 5:28 PMshikasd
05/28/2024, 10:04 PMChuck Jazdzewski [G]
05/28/2024, 10:15 PMFelix
05/29/2024, 4:38 PMIf you look at the code for a composable function, you might assume that the code is run in the order it appears. But this isn't necessarily true.This sentence seems a bit scary, if the scope of the behaviour changes is not more well defined. Note that it refers to the code for a composable function and not the order on how those functions are called.
Chuck Jazdzewski [G]
05/29/2024, 6:34 PM@Composable
functions are normal function except that they 1) can skip if their parameters are the same as before (e.g. a memoized result), 2) can be called again directly if they read a mutable state that is changed (even if their parameters have not), and carry a silent parameter, the composer, which records the implied effect of composition. During initial composition (when it is creating the content, not updating it) the code executes without any reordering. When we recompose (to run the code to update the content) we ensure that all calls happen in the order they would have had the caller called them directly. That is, StartScreen()
is always called before MiddleScreen()
if both are called in the same composition. However, StartScreen()
is not guaranteed to be called if MiddleScreen()
is called as it may skip or the caller may have skipped, etc. Also, we guarantee that callers are executed before the code it calls. That is if MyFancyNavigation
need to execute in the same composition as StartScreen
it will execute before StartScreen
(and the other calls made in the lambda). These guarantees are for correctness. That is if we didn't execute MyFancyNavigation
first then we would not be sure the it even calls StartScreen
for example, as it may not call the lambda in some cases. By correctness I mean that (ignoring mutable side effect outside the snapshot system) it would produce the same composition had all the functions been called without skipping or reordering.
Composable lambdas, however, are called when the caller calls them. No surprise there. But they may be called outside of normal composition. This happens most often with what is referred as sub-composition as with BoxWithConstraints()
and LazyColumn()
. Any lambda may change in what phase of composition it is called. Mostly they are called synchronously but they may not be. This is no different from any other lambda in Kotlin but I call it out as it seems to contradict the paragraph above. Also, with the DSL syntax, lambdas can often be hidden so it often requires knowledge of what the composable function does to understand when something will execute. This is also like the rest of Kotlin but we lean on it a bit more heavily than other libraries.
That said, it is best not to rely on these ordering guarantees. As the document implies, you should write in a style that is resilient to reordering of the execution and not rely on side-effects that would change if the order of execution changed. It is best to think of @Composable
functions as pure functions, in the monadic sense, where the only side effects they have are recorded by the composer. An @Composable
function should only produce the content it declares without any other effect.
The order in which compose executes functions is deterministic and predictable, in a theoretical sense, but that doesn't mean, in practice, this is particularly easy to do or should be relied on.Felix
05/30/2024, 9:08 AM