Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Understanding Compose

Understanding Compose

This talk covers the benefits of a declarative reactive UI system like Jetpack Compose and how it applies to real problems that Android developers have today. Additionally, this talk expands on the programming model of Jetpack Compose and some of its implementation details the can help you understand how Compose works.

Leland Richardson

October 24, 2019
Tweet

More Decks by Leland Richardson

Other Decks in Programming

Transcript

  1. Leland Richardson @intelligibabble Understanding Compose My name is Leland Richardson.

    I’m a software engineer on the Android UI Toolkit team working on Compose. More specifically, I mostly work on the Compose Runtime and Compiler.
  2. 2019 2008 Expectations for UI are growing As app developers,

    the expectations of the apps that we build has changed dramatically since Android has launched. The User Interfaces of a typical Android app are far more dynamic, complex and polished to a level that wasn’t achievable 10 years ago, let alone expected. We believe Compose is a powerful tool that can help app developers build apps in this way. So in this talk, I’d like to go over a few things that I think will help you understand Compose a little bit better…
  3. What problems does Compose solve? …by going over some of

    the problems that we think Compose solves. I’m going to cover some of the reasoning behind the design of Compose, and what makes it powerful.
  4. How do I use Compose? Covering topics such as “How

    do I think about code I write with compose?” And “What is the mental model of compose?”
  5. How does Compose work? And finally, I’d like to dive

    in and go over how compose actually works under the hood. Going into detail about the runtime and compiler and some of the choices that we made that make Compose unique from other similar frameworks.
  6. Separation of Concerns So to frame this, I’d like to

    talk a bit about “Separation of Concerns”. Separation of Concerns is considered one of the fundamental principles of good software design. The principle of Separation of Concerns has been around for a long time (over 40 years!). One of the original postulations of this principle was actually put in terms of two other words:
  7. Cohesion vs Coupling “Coupling” and “Cohesion”. I find these are

    more concrete terms and easier to define, whereas “Separation of Concerns” can sometimes feel subjective and can vary.
  8. module unit To define these things, we want to break

    software down into two denominations. A “Unit” and a “Module”. A Module is made up of several units. A program is made up of several modules.
  9. Coupling When we have two or more modules, we often

    can end up with dependencies between the two. This type of dependency we can refer to as “Coupling” between the two modules. The coupling itself can take many different forms. For instance, sometimes the coupling between the two modules is implicit. Generally speaking, we want to *minimize* coupling. It cannot be completely removed, but less is better.
  10. Cohesion We can contrast this with dependencies, or relatedness, between

    units of a given module. This is referred to as “cohesion”. Generally speaking, we want to maximize cohesion.
  11. layout.xml ViewModel Now let’s apply this to something a little

    bit more concrete, and perhaps familiar to a lot of us here. We have on the left here our ViewModel. Some class or set of classes containing the data we would like to display to the user. And on the right we have our corresponding layout xml definition. As this UI is dynamic and is displaying the data of our ViewModel, inevitably we might end up with some coupling here.
  12. layout.xml ViewModel findViewById(R.id.my_text_view) view.getChildAt(0) This coupling can take many forms.

    For instance, a very common form is the use of `findViewById`. Even worse, at times code might be required that relies on the specific structure that has been defined in the layout, for instance by using `getChildAt`.
  13. layout.xml ViewModel As our UI grows in complexity, or our

    App’s specific needs increase, this coupling will grow and grow. At the moment, this is very hard to avoid. As the UI gets more and more dynamic, the structure of the UI at runtime may start to diverge from the UI defined in the layout…
  14. layout.xml ViewModel </> As we can see, the problem here

    is that there is naturally a tight coupling between the layout and a view model. Since layouts are defined in XML and ViewModels in Kotlin…
  15. Language creates a forced separation …it creates a forced line

    of separation between the layout XML. If we accept that the ViewModel and the markup are related, then there will always be a coupling
  16. layout.kt ViewModel Moreover, once this line of separation is removed,

    we are free to reorganize and refactor our code in such a way…
  17. Wait. …so some of you might hear this and have

    a few alarm bells going off in your head…
  18. Are you saying we should mix Logic and UI? …And

    you might think this sounds a lot like we are mixing Logic and UI… Here’s the thing…
  19. A framework cannot separate your concerns for you A framework

    cannot perfectly enforce separation of concerns. Every code base is different.
  20. Only you can It is better if we let you

    draw the lines of separation for what makes the most sense for your app.
  21. But we can provide you with tools to make it

    easier but, what we can do is provide you with tools and an architecture that makes this *easier* for you to do.
  22. What’s the tool?
 The @Composable function. And with compose, that

    tool is the @Composable function. The benefit here is that this should be somewhat familiar to all of you because a @Composable function is, well… a function. Refactoring functions, making new functions, and extracting logic into functions is something that most of you should already be quite familiar with doing. Functions handle this really well, and we want to embrace that.
  23. @Composable fun App(appData: AppData) { val derivedData = compute(appData) Header()

    if (appData.isOwner) { EditButton() } Body { for (item in derivedData.items) { Item(item) } } }
  24. @Composable fun App(appData: AppData) { val derivedData = compute(appData) Header()

    if (appData.isOwner) { EditButton() } Body { for (item in derivedData.items) { Item(item) } } }
  25. @Composable fun App(appData: AppData) { val derivedData = compute(appData) Header()

    if (appData.isOwner) { EditButton() } Body { for (item in derivedData.items) { Item(item) } } }
  26. @Composable fun App(appData: AppData) { val derivedData = compute(appData) Header()

    if (appData.isOwner) { EditButton() } Body { for (item in derivedData.items) { Item(item) } } }
  27. @Composable fun App(appData: AppData) { val derivedData = compute(appData) Header()

    if (appData.isOwner) { EditButton() } Body { for (item in derivedData.items) { Item(item) } } }
  28. Declarative A composable function creates a declarative API. Now, this

    is one of those buzz words, so let’s dig in to what is really meant by this.
  29. Declarative vs Imperative Typically, when we talk about being declarative,

    we are talking about it in contrast to something being “imperative”. Let’s compare these two by means of an example.
  30. 8 99+ Consider we have a UI for a mail

    or chat application and we have a “unread messages” icon. The icon is an empty envelope if there are 0 unread messages, and if there are more than one messages, it has a piece of paper in the icon and a badge showing the number of messages. Finally, if the number of unread messages is 100 or more, we add some fire to the icon and just render “99+” instead of the exact number.
  31. fun updateCount(count: Int) { if (count > 0 && !hasBadge())

    { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { removePaper() } else if (count == 0 && hasPaper()) { addPaper() } if (count <= 99) { setBadgeText("$count") } } With an imperative approach, the logic for this UI might look something like this. When we get an updated count to display in the UI, each facet of the corresponding dependent elements of the UI become a conditional check to add, remove, or update that element. 
 
 For a seemingly simple example, it’s surprising how much logic is actually required to implement this correctly.
  32. @Composable fun BadgedEnvelope(count: Int) { Envelope(fire=count > 99, paper=count >

    0) { if (count > 0) { Badge(text="$count") } } } With a declarative approach, the code needed to implement this example goes down considerably. In this case our function similarly receives count as a parameter, but instead of making a bunch of checks and conditionals, it simply describes the conditions for the fire to exist, the paper to exist, and the badge to exist and passes them in to other composable functions as parameters.
  33. Concerns of a UI Developer - Given data, what UI

    would you like to show? - How to respond to events - How your UI changes over time The critical point here is the a declarative approach changes things such that you know longer have to consider what the current state of your UI is in order to get it into the next state. The runtime handles state transitions for you. 
 
 This is a huge chunk of current cognitive overhead.
  34. Composition Next, let’s talk about composition.
 
 So clearly, with

    a name like “Compose” and these “@Composable” annotations all around, this concept of “Composition” must be important. What exactly is meant by this?
  35. Composition vs Inheritance Many UI libraries are built in languages

    with an object oriented programming model which tends to push people towards inheritance as a composition model. Compose moves away from this in favor of functional composition.
  36. class Input : View() { /* ... */ } class

    ValidatedInput : Input() { /* ... */ } class DateInput : ValidatedInput() { /* ... */ } class DateRangeInput : ??? { /* ... */ } Inheritance
  37. @Composable fun <T> Input(value: T, onChange: (T) -> Unit) {

    /* ... */ } @Composable fun ValidatedInput(value: T, onChange: (T) -> Unit, isValid: Boolean) { InputDecoration(color=if(isValid) blue else red) { Input(value, onChange) } } Compose’s Composition Model
  38. @Composable fun DateInput(value: DateTime, onChange: (DateTime) -> Unit) { ValidatedInput(

    value, onChange = { ... onChange(...) }, isValid = isValidDate(value) ) } @Composable fun DateRangeInput(value: DateRange, onChange: (DateRange) -> Unit) { DateInput(value=value.start, ...) DateInput(value=value.end, ...) } Compose’s Composition Model
  39. class FancyBox : View() { /* ... */ } class

    FancyEditForm : ??? { /* ... */ } class EditForm : FormView() { /* ... */ } class FancyStory : ??? { /* ... */ } class Story : View() { /* ... */ } Inheritance
  40. @Composable fun FancyBox(children: @Composable () -> Unit) { Box(fancy) {

    children() } } @Composable fun FancyEditForm(...) { FancyBox { EditForm(...) } } @Composable fun FancyStory(...) { FancyBox { Story(…) } } @Composable fun EditForm(...) { /* ... */ } @Composable fun Story(…) { /* ... */ } Compose’s Composition Model
  41. @Composable fun Counter() { val count = +state { 0

    } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) } @Model class State<T>( val value: T )
  42. Alters Function Type // function declaration suspend fun MyFun() {

    … } // lambda declaration val myLambda = suspend { … } // function type fun MyFun(myParam: suspend () -> Unit) { … }
  43. Alters Function Type // function declaration @Composable fun MyFun() {

    … } // lambda declaration val myLambda = @Composable { … } // function type fun MyFun(myParam: @Composable () -> Unit) { … }
  44. Requires Calling Context fun Example(a: () -> Unit, b: suspend

    () -> Unit) { a() // allowed b() // NOT allowed } suspend fun Example(a: () -> Unit, b: suspend () -> Unit) { a() // allowed b() // allowed }
  45. Requires Calling Context fun Example(a: () -> Unit, b: @Composable

    () -> Unit) { a() // allowed b() // NOT allowed } @Composable fun Example(a: () -> Unit, b: @Composable () -> Unit) { a() // allowed b() // allowed }
  46. @Composable fun Counter() { val count = +state { 0

    } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) }
  47. fun Counter($composer: Composer, $key: Int) { val count = +state

    { 0 } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) }
  48. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state { 0 } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() }
  49. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() }
  50. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY
  51. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Group($key)
  52. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY EMPTY Group($key) Group(123) EMPTY
  53. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY EMPTY Group($key) Group(123) State(0)
  54. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY EMPTY Group($key) Group(123) State(0)
  55. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY EMPTY Group($key) Group(123) State(0) Group(456)
  56. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY EMPTY Group($key) Group(123) State(0) Group(456) "Count: 0"
  57. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... EMPTY EMPTY Group($key) Group(123) State(0) Group(456) "Count: 0" { … }
  58. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... Group($key) Group(123) State(0) Group(456) "Count: 0" { … } Button(…)
  59. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } ... ... Group($key) Group(123) State(0) Group(456) "Count: 0" { … } Button(…)
  60. fun Counter($composer: Composer, $key: Int) { $composer.start($key) val count =

    +state($composer, 123) { 0 } Button($composer, 456, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() } state(…) Button(…) ... ... Group($key) Group(123) State(0) Group(456) "Count: 0" { … } Button(…)
  61. @Composable fun App() { val result = getData() if (result

    == null) { Loading(...) } else { Header(result) Body(result) } }
  62. fun App($composer: Composer) { val result = getData() if (result

    == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } }
  63. ... fun App($composer: Composer) { val result = getData() if

    (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } // result = null EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY EMPTY
  64. ... fun App($composer: Composer) { val result = getData() if

    (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY Group(123) // result = null
  65. ... fun App($composer: Composer) { val result = getData() if

    (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... EMPTY Group(123) Loading(…) // result = null
  66. ... fun App($composer: Composer) { val result = getData() if

    (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... Group(123) Loading(…) ... // result = null
  67. ... Group(123) ... Loading(…) fun App($composer: Composer) { val result

    = getData() if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY Gap EMPTY ... App(…)
  68. ... Group(123) ... Loading(…) fun App($composer: Composer) { val result

    = getData() if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... // result = FeedItem(…)
  69. ... Group(123) ... Loading(…) fun App($composer: Composer) { val result

    = getData() if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... // result = FeedItem(…)
  70. ... Group(123) ... Loading(…) fun App($composer: Composer) { val result

    = getData() if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... // result = FeedItem(…)
  71. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY // result = FeedItem(…)
  72. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY Group(456) // result = FeedItem(…)
  73. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY EMPTY EMPTY ... Group(456) Header(…) // result = FeedItem(…)
  74. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY ... Group(456) Header(…) Body(…) // result = FeedItem(…)
  75. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY ... Group(456) Header(…) Body(…) // result = FeedItem(…)
  76. ... ... fun App($composer: Composer) { val result = getData()

    if (result == null) { $composer.start(123) Loading(...) $composer.end() } else { $composer.start(456) Header(result) Body(result) $composer.end() } } EMPTY EMPTY ... Group(456) Header(…) Body(…) App(…) // result = FeedItem(…)
  77. @Composable fun App(items: List<String>, query: String) { val results =

    items.filter { it.matches(query) } // ... } Positional Memoization
  78. @Composable fun App(items: List<String>, query: String) { val results =

    +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } ... EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY EMPTY Positional Memoization
  79. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY @Composable

    fun App(items: List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } EMPTY Positional Memoization
  80. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY EMPTY @Composable

    fun App(items: List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } ["a", "b", "c"] Positional Memoization
  81. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY EMPTY @Composable fun

    App(items: List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" Positional Memoization ["a", "b", "c"]
  82. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  83. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  84. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  85. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  86. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  87. ... EMPTY EMPTY EMPTY EMPTY ... EMPTY @Composable fun App(items:

    List<String>, query: String) { val results = +memo(items, query) { 
 items.filter { it.matches(query) }
 } // ... } "b" ["b"] Positional Memoization ["a", "b", "c"]
  88. @Composable fun App() { val x = +memo { Math.random()

    } // ... } Positional Memoization
  89. @Model class State<T>(var value: T) @Composable fun <T> state(initial: ()

    -> T) = memo { State(initial()) } Positional Memoization
  90. @Composable fun Google( number: Int ) { Address( number=number, street="Amphitheatre

    Pkwy", city="Mountain View", state="CA" zip="94043" } @Composable fun Address( number: Int, street: String, city: String, state: String, zip: String ) { Text("$number $street") Text(city) Text(", ") Text(state) Text(" ") Text(zip) } ... ... "Mountain View" "Mountain View" ", " "CA" "CA" " " "94043" ...
  91. fun Google( $composer: Composer, number: Int ) { Address( $composer,

    number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" ) }
  92. fun Google( $composer: Composer, $static: Int, number: Int ) {

    Address( $composer, 0b11110 or ($static and 0b1), number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" ) }
  93. fun Address( $composer: Composer, number: Int, street: String, city: String,

    state: String, zip: String ) { Text($composer, "$number $street") Text($composer, city) Text($composer, ", ") Text($composer, state) Text($composer, " ") Text($composer, zip) }
  94. fun Address( $composer: Composer, $static: Int, number: Int, street: String,

    city: String, state: String, zip: String ) { Text($composer, ($static and 0b11) and (($static and 0b10) shr 1), "$number $street") Text($composer, ($static and 0b100) shr 2, city) Text($composer, 0b1, ", ") Text($composer, ($static and 0b1000) shr 3, state) Text($composer, 0b1, " ") Text($composer, ($static and 0b10000) shr 4, zip) }
  95. ... ... @Composable fun Google( number: Int ) { Address(

    number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" } @Composable fun Address( number: Int, street: String, city: String, state: String, zip: String ) { Text("$number $street") Text(city) Text(", ") Text(state) Text(" ") Text(zip) } "Mountain View" "Mountain View" ", " "CA" "CA" " " "94043" ... Redundant!
  96. ... ... @Composable fun Google( number: Int ) { Address(

    number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" } @Composable fun Address( number: Int, street: String, city: String, state: String, zip: String ) { Text("$number $street") Text(city) Text(", ") Text(state) Text(" ") Text(zip) } "Mountain View" ", " "CA" " " "94043" ... Static!
  97. ... @Composable fun Google( number: Int ) { Address( number=number,

    street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" } @Composable fun Address( number: Int, street: String, city: String, state: String, zip: String ) { Text("$number $street") Text(city) Text(", ") Text(state) Text(" ") Text(zip) } ... 1600
  98. @Composable fun Google( number: Int ) { Address( number=number, street="Amphitheatre

    Pkwy", city="Mountain View", state="CA" zip="94043" ) }
  99. fun Google( $composer: Composer, number: Int ) { Address( $composer,

    number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" ) }
  100. fun Google( $composer: Composer, number: Int ) { if (number

    == $composer.next()) { Address( $composer, number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" ) } else { $composer.skip() } }
  101. @Composable fun Counter() { val count = +state { 0

    } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) }
  102. fun Counter($composer: Composer) { $composer.start() val count = +state($composer) {

    0 } Button($composer, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end() }
  103. 
 fun Counter($composer: Composer) { $composer.start() val count = +state($composer)

    { 0 } Button($composer, text="Count: ${count.value}" onPress={ count.value += 1 } ) $composer.end()?.updateScope { nextComposer -> Counter(nextComposer) } }