Slide 1

Slide 1 text

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.

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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…

Slide 4

Slide 4 text

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.

Slide 5

Slide 5 text

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?”

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

What problems does Compose solve?

Slide 8

Slide 8 text

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:

Slide 9

Slide 9 text

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.

Slide 10

Slide 10 text

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.

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

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`.

Slide 15

Slide 15 text

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…

Slide 16

Slide 16 text

layout.xml ViewModel … meaning that things like `findViewById` could lead to null reference exceptions

Slide 17

Slide 17 text

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…

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

layout.kt ViewModel However, what happens if we define our layouts in Kotlin?

Slide 20

Slide 20 text

layout.kt ViewModel This means that the forced separation and implicit coupling we had before becomes explicit…

Slide 21

Slide 21 text

layout.kt ViewModel Moreover, once this line of separation is removed, we are free to reorganize and refactor our code in such a way…

Slide 22

Slide 22 text

layout.kt ViewModel …that we can reduce coupling, and increase cohesion between the various concerns of our UI.

Slide 23

Slide 23 text

Wait. …so some of you might hear this and have a few alarm bells going off in your head…

Slide 24

Slide 24 text

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…

Slide 25

Slide 25 text

A framework cannot separate your concerns for you A framework cannot perfectly enforce separation of concerns. Every code base is different.

Slide 26

Slide 26 text

Only you can It is better if we let you draw the lines of separation for what makes the most sense for your app.

Slide 27

Slide 27 text

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.

Slide 28

Slide 28 text

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.

Slide 29

Slide 29 text

Anatomy of a Composable

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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.

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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.

Slide 39

Slide 39 text

@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.

Slide 40

Slide 40 text

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.

Slide 41

Slide 41 text

Describe the UI based on the provided parameters

Slide 42

Slide 42 text

One definition describes all possible states.

Slide 43

Slide 43 text

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?

Slide 44

Slide 44 text

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.

Slide 45

Slide 45 text

class Input : View() { /* ... */ } class ValidatedInput : Input() { /* ... */ } class DateInput : ValidatedInput() { /* ... */ } class DateRangeInput : ??? { /* ... */ } Inheritance

Slide 46

Slide 46 text

@Composable fun 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

Slide 47

Slide 47 text

@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

Slide 48

Slide 48 text

class FancyBox : View() { /* ... */ } class FancyEditForm : ??? { /* ... */ } class EditForm : FormView() { /* ... */ } class FancyStory : ??? { /* ... */ } class Story : View() { /* ... */ } Inheritance

Slide 49

Slide 49 text

@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

Slide 50

Slide 50 text

Encapsulation

Slide 51

Slide 51 text

A composable is given parameters. A composable manages state.

Slide 52

Slide 52 text

State flows down via parameters. State changes flow up via callbacks.

Slide 53

Slide 53 text

Recomposition

Slide 54

Slide 54 text

fun bind(liveMsgs: LiveData) { liveMsgs.observe(this) { msgs -> updateBody(msgs) } }

Slide 55

Slide 55 text

@Composable fun Messages(liveMsgs: LiveData) { val msgs = +observe(liveMsgs) for (msg in msgs) { Message(msg) } }

Slide 56

Slide 56 text

@Composable fun Counter() { val count = +state { 0 } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) } @Model class State( val value: T )

Slide 57

Slide 57 text

How does Compose work?

Slide 58

Slide 58 text

Disclaimer

Slide 59

Slide 59 text

Implementation Details To Follow

Slide 60

Slide 60 text

What does @Composable
 actually do?

Slide 61

Slide 61 text

An analogy: suspend

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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 }

Slide 65

Slide 65 text

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 }

Slide 66

Slide 66 text

Execution Model

Slide 67

Slide 67 text

Gap Buffer

Slide 68

Slide 68 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer

Slide 69

Slide 69 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0

Slide 70

Slide 70 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1

Slide 71

Slide 71 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2

Slide 72

Slide 72 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2 3

Slide 73

Slide 73 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2 3

Slide 74

Slide 74 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2 3

Slide 75

Slide 75 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2 3

Slide 76

Slide 76 text

EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 2 3

Slide 77

Slide 77 text

EMPTY EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 3 4 2

Slide 78

Slide 78 text

EMPTY EMPTY EMPTY EMPTY Gap Gap Buffer 0 1 4 5 2 3

Slide 79

Slide 79 text

@Composable fun Counter() { val count = +state { 0 } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) }

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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() }

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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)

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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)

Slide 87

Slide 87 text

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)

Slide 88

Slide 88 text

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)

Slide 89

Slide 89 text

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"

Slide 90

Slide 90 text

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" { … }

Slide 91

Slide 91 text

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(…)

Slide 92

Slide 92 text

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(…)

Slide 93

Slide 93 text

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(…)

Slide 94

Slide 94 text

state(…) Counter(…) Button(…) ... ... Group($key) Group(123) State(0) Group(456) "Count: 0" { … } Button(…)

Slide 95

Slide 95 text

... ... State(0) "Count: 0" { … } Button(…) state(…) Counter(…) Button(…)

Slide 96

Slide 96 text

@Composable fun App() { val result = getData() if (result == null) { Loading(...) } else { Header(result) Body(result) } }

Slide 97

Slide 97 text

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() } }

Slide 98

Slide 98 text

... 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

Slide 99

Slide 99 text

... 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

Slide 100

Slide 100 text

... 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

Slide 101

Slide 101 text

... 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

Slide 102

Slide 102 text

... 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(…)

Slide 103

Slide 103 text

... 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(…)

Slide 104

Slide 104 text

... 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(…)

Slide 105

Slide 105 text

... 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(…)

Slide 106

Slide 106 text

... ... 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(…)

Slide 107

Slide 107 text

... ... 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(…)

Slide 108

Slide 108 text

... ... 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(…)

Slide 109

Slide 109 text

... ... 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(…)

Slide 110

Slide 110 text

... ... 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(…)

Slide 111

Slide 111 text

... ... 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(…)

Slide 112

Slide 112 text

Positional Memoization

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

@Composable fun memo(vararg inputs: Any?, fn: () -> T): T Positional Memoization

Slide 125

Slide 125 text

@Composable fun App() { val x = +memo { Math.random() } // ... } Positional Memoization

Slide 126

Slide 126 text

@Model class State(var value: T) @Composable fun state(initial: () -> T) = memo { State(initial()) } Positional Memoization

Slide 127

Slide 127 text

@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" ...

Slide 128

Slide 128 text

fun Google( number: Int ) { Address( number=number, street="Amphitheatre Pkwy", city="Mountain View", state="CA" zip="94043" ) }

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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" ) }

Slide 131

Slide 131 text

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) }

Slide 132

Slide 132 text

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) }

Slide 133

Slide 133 text

... ... @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!

Slide 134

Slide 134 text

... ... @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!

Slide 135

Slide 135 text

... @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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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() } }

Slide 139

Slide 139 text

Recomposition

Slide 140

Slide 140 text

@Composable fun Counter() { val count = +state { 0 } Button( text="Count: ${count.value}" onPress={ count.value += 1 } ) }

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text


 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) } }

Slide 143

Slide 143 text

Closing Thoughts

Slide 144

Slide 144 text

Still Early!

Slide 145

Slide 145 text

Compiler not optional

Slide 146

Slide 146 text

Join the conversation! slack.kotlinlang.org #compose

Slide 147

Slide 147 text

Thank You! Leland Richardson @intelligibabble