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

Playing in the Treehouse with Redwood and Zipli...

Playing in the Treehouse with Redwood and Zipline (KotlinConf 2023)

Redwood is Cash App's multiplatform Compose library which targets the native UI toolkit of each platform while still sharing presentation logic. This means our app's existing Android views and iOS UIViews can be powered by common Compose and still offer a path to Compose UI and Swift UI.

Zipline is a multiplatform Javascript engine for Android, iOS, and the JVM which uses Kotlin interfaces for calls in and out of JS. This allows us to update the logic of our apps faster than going through the app store release process.

Treehouse is what we call the combination of Redwood and Zipline. By moving the Compose presentation logic into Zipline we can update the screens of our apps across Android, iOS, and web without waiting for the app store.

This talk will cover how Redwood and Zipline can work in isolation but how the combination as Treehouse is their most powerful form.

Video: https://jakewharton.com/playing-in-the-treehouse-with-redwood-and-zipline/

Jake Wharton

April 14, 2023
Tweet

More Decks by Jake Wharton

Other Decks in Technology

Transcript

  1. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  2. Redwood Schema data class Column( val children: () -> Unit,

    val birthday: LocalDate, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  3. Redwood Schema import kotlinx.datetime.LocalDate data class Column( val children: ()

    -> Unit, val birthday: LocalDate, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  4. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  5. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  6. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  7. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  8. Redwood Schema data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  9. Redwood Schema data class Row(…) data class Column(…) data class

    Image( val url: HttpUrl, val size: ImageSize, val borderStyle: BorderStyle, ) data class Text( val text: String, val font: FontFamily, val style: FontStyle, )
  10. Redwood Schema data class ContactItem( val name: String, val image:

    HttpUrl, val content: () -> Unit, ) data class Text( val text: String, val font: FontFamily, val style: FontStyle, )
  11. Redwood Schema data class Row(…) data class Column(…) data class

    Image( val url: HttpUrl, val size: ImageSize, val borderStyle: BorderStyle, ) data class Text( val text: String, val font: FontFamily, val style: FontStyle, ) data class ContactItem( val name: String, val image: HttpUrl, val content: () -> Unit, ) data class Text( val text: String, val font: FontFamily, val style: FontStyle, )
  12. Redwood Compose data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  13. Redwood Compose data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) @Composable fun Column( children: @Composable () -> Unit, ) { … } @Composable fun TextInput( hint: String, text: String, onTextChanged: (String) -> Unit, ) { … }
  14. Redwood Compose @Composable fun Column( children: @Composable () -> Unit,

    ) { … } @Composable fun TextInput( hint: String, text: String, onTextChanged: (String) -> Unit, ) { … }
  15. Redwood Compose Column { var query by remember { mutableStateOf("")

    } TextInput( hint = "Search", text = query, onTextChanged = { query = it }, ) val images = LoadImages(query) ScrollableColumn { for (image in images) { Image(url = image.url) } } }
  16. Redwood Widgets data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  17. Redwood Widgets data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> } interface TextInput<T : Any> : Widget<T> { fun hint(hint: String) fun text(text: String) fun onTextChanged(onTextChanged: ((String) -> Unit)?) }
  18. Redwood Widgets interface Column<T : Any> : Widget<T> { val

    children: Widget.Children<T> } interface TextInput<T : Any> : Widget<T> { fun hint(hint: String) fun text(text: String) fun onTextChanged(onTextChanged: ((String) -> Unit)?) }
  19. Redwood Widgets interface Widget<T : Any> { val value: T

    } interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> } interface TextInput<T : Any> : Widget<T> { fun hint(hint: String) fun text(text: String) fun onTextChanged(onTextChanged: ((String) -> Unit)?) }
  20. Redwood Widgets class ViewColumn : Column<View> interface Column<T : Any>

    : Widget<T> { val children: Widget.Children<T> }
  21. Redwood Widgets class ViewColumn( override val value: LinearLayout, ) :

    Column<View> interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> }
  22. Redwood Widgets class ViewColumn( override val value: LinearLayout, ) :

    Column<View> { override val children = ViewGroupChildren(value) } interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> }
  23. Redwood Widgets interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun

    Column(): Column<T> fun ScrollableColumn(): ScrollableColumn<T> fun TextInput(): TextInput<T> fun Image(): Image<T> }
  24. Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :

    EmojiWidgetFactory<View> interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … }
  25. Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :

    EmojiWidgetFactory<View> { override fun Column(): Column<View> { } } interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … }
  26. Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :

    EmojiWidgetFactory<View> { override fun Column(): Column<View> { return ViewColumn(LinearLayout(context)) } } interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … }
  27. Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :

    EmojiWidgetFactory<View> { override fun Column(): Column<View> { return ViewColumn(LinearLayout(context)) } … } interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … }
  28. @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp))

    { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape), ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2, ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.body2, ) } } } }
  29. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } }
  30. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root
  31. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root
  32. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  33. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  34. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Image
  35. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Spacer Image
  36. Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)

    Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  37. Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)

    Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  38. Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)

    Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text
  39. Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)

    Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text Spacer
  40. Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {

    Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  41. Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {

    Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row
  42. Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {

    Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text
  43. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

    Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Spacer Image Column Text Spacer Surface Text
  44. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

    LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView
  45. Root Row Spacer Image Column Text Spacer Surface Text UIStackView

    UIStackView UIView UIImageView UIStackView UITextView UIView UIView UITextView
  46. Root Row Spacer Image Column Text Spacer Surface Text <div>

    <div> <div> <img> <div> <span> <div> <div> <span>
  47. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) { … }
  48. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) {
 ComposeNode<…>( factory = …, update = { … }, ) }
  49. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) {
 RedwoodComposeNode<…>( factory = …, update = { … }, ) }
  50. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) {
 RedwoodComposeNode<EmojiWidgetFactory<*>, …>( factory = …, update = { … }, ) }
  51. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) {
 RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = …, update = { … }, ) }
  52. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) {
 RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = EmojiWidgetFactory<*>::TextInput, update = { … }, ) }
  53. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = EmojiWidgetFactory<*>::TextInput, update = { set(hint) { hint(hint) } set(text) { text(text) } set(onTextChanged) { onTextChanged(onTextChanged) } }, ) }
  54. Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:

    String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = EmojiWidgetFactory<*>::TextInput, update = { set(hint) { hint(hint) } set(text) { text(text) } set(onTextChanged) { onTextChanged(onTextChanged) } }, ) }
  55. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

    LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView
  56. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

    LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView
  57. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

    LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView Text(msg.body, …)
  58. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

    LinearLayout View ImageView LinearLayout View FrameLayout TextView TextView Text(msg.body, …)
  59. Redwood Layout data class Row( val width: Constraint, val height:

    Constraint, val margin: Margin, val overflow: Overflow, val horizontalAlignment: MainAxisAlignment, val verticalAlignment: CrossAxisAlignment, val children: RowScope.() -> Unit, ) data class Column(…) data class Spacer(…)
  60. Redwood Counter Sample data class Text( val text: String?, )

    data class Button( val text: String?, val enabled: Boolean = true, val onClick: (() -> Unit)? = null, )
  61. Redwood Counter Sample @Composable fun Counter(value: Int = 0) {

    var count by remember { mutableStateOf(value) } Column { Button("-1", onClick = { count-- }) Text("Count: $count") Button("+1", onClick = { count++ }) } }
  62. Redwood Counter Sample @Composable fun Counter(value: Int = 0) {

    var count by remember { mutableStateOf(value) } Column { Button("-1", onClick = { count-- }) Text("Count: $count") Button("+1", onClick = { count++ }) } }
  63. Redwood Counter Sample @Composable fun Counter(value: Int = 0) {

    var count by remember { mutableStateOf(value) } Column { Button("-1", onClick = { count-- }) Text("Count: $count") Button("+1", onClick = { count++ }) } }
  64. Redwood Counter Sample @Composable fun Counter(value: Int = 0) {

    var count by remember { mutableStateOf(value) } Column { Button("-1", onClick = { count-- }) Text("Count: $count") Button("+1", onClick = { count++ }) } }
  65. Redwood Counter Sample @Composable fun Counter(value: Int = 0) {

    var count by remember { mutableStateOf(value) } Column { Button("-1", onClick = { count-- }) Text("Count: $count") Button("+1", onClick = { count++ }) } }
  66. Redwood Counter Sample class AndroidText( override val value: TextView, )

    : Text<View> { override fun text(text: String?) { value.text = text } }
  67. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), )
  68. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), )
  69. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), )
  70. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), )
  71. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), )
  72. Redwood Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) composition.setContent { Counter() }
  73. Redwood Counter Sample class ComposeUiText : Text<@Composable () -> Unit>

    { private var text by mutableStateOf("") override val value = @Composable { Text(text = text) } override fun text(text: String?) { this.text = text ?: "" } }
  74. Redwood Counter Sample val factories = SchemaWidgetFactories( Counter = ComposeUiCounterWidgetFactory,

    RedwoodLayout = ComposeUiRedwoodLayoutWidgetFactory(), ) setContent { CounterTheme { RedwoodContent(factories) { Counter() } } }
  75. Redwood Counter Sample val factories = SchemaWidgetFactories( Counter = ComposeUiCounterWidgetFactory,

    RedwoodLayout = ComposeUiRedwoodLayoutWidgetFactory(), ) setContent { CounterTheme { RedwoodContent(factories) { Counter() } } }
  76. Redwood Counter Sample val factories = SchemaWidgetFactories( Counter = ComposeUiCounterWidgetFactory,

    RedwoodLayout = ComposeUiRedwoodLayoutWidgetFactory(), ) setContent { CounterTheme { RedwoodContent(factories) { Counter() } } }
  77. Redwood Counter Sample application { Window( onCloseRequest = ::exitApplication, title

    = "Counter", state = rememberWindowState(300.dp, 300.dp), ) { CounterTheme { RedwoodContent(factories) { Counter() } } } }
  78. Redwood Counter Sample class HtmlText( override val value: HTMLSpanElement, )

    : Text<HTMLElement> { override fun text(text: String?) { value.textContent = text } }
  79. Redwood Counter Sample class IosText : Text<UIView> { override val

    value = UILabel().apply { textAlignment = NSTextAlignmentCenter } override fun text(text: String?) { value.text = text } }
  80. class PaymentRenderer { fun render(payment: Payment): String { … }

    } PaymentRenderer { fun render(payment: Payment): String { }
  81. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { … } } interface PaymentRenderer { fun render(payment: Payment): String }
  82. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { … } } interface PaymentRenderer : ZiplineService { fun render(payment: Payment): String }
  83. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { … } } interface PaymentRenderer : ZiplineService { fun render(payment: Payment): String }
  84. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { … } } zipline.bind<PaymentRenderer>(RealPaymentRenderer())
  85. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { … } } zipline.bind<PaymentRenderer>(RealPaymentRenderer()) val renderer = zipline.take<PaymentRenderer>() println(renderer.render(Payment(…))
  86. class RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String

    { /* Fancy new impl */ } } zipline.bind<PaymentRenderer>(RealPaymentRenderer()) val renderer = zipline.take<PaymentRenderer>() println(renderer.render(Payment(…))
  87. zipline.bind<LocationManager>(SystemLocationManager(…)) // Load Kotlin/JS val renderer = zipline.take<PaymentRenderer>() println(renderer.render(Payment(…)) class

    RealPaymentRenderer : PaymentRenderer { override fun render(payment: Payment): String { /* Fancy new impl */ } } zipline.bind<PaymentRenderer>(RealPaymentRenderer())
  88. zipline.bind<LocationManager>(SystemLocationManager(…)) // Load Kotlin/JS val renderer = zipline.take<PaymentRenderer>() println(renderer.render(Payment(…)) class

    RealPaymentRenderer( private val location: LocationManager, ) : PaymentRenderer { override fun render(payment: Payment): String { /* Fancy new impl */ } } val location = zipline.take<LocationManager>() zipline.bind<PaymentRenderer>(RealPaymentRenderer(location))
  89. interface SomeService : ZiplineService { fun sync(in: String): String suspend

    fun async(in: String): String fun stream(in: String): Flow<String> }
  90. interface SomeService : ZiplineService { fun sync(in: String): String suspend

    fun async(in: String): String fun stream(in: String): Flow<String> fun stateStream(in: String): StateFlow<String> }
  91. interface SomeService : ZiplineService { fun sync(in: String): String suspend

    fun async(in: String): String fun stream(in: Flow<String>): String fun stateStream(in: StateFlow<String>): String }
  92. interface SomeService : ZiplineService { fun sync(in: String): String suspend

    fun async(in: String): String fun stream(in: Flow<String>): String fun stateStream(in: StateFlow<String>): String fun userTypes(in: Payment): RenderedPayment }
  93. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    } f u n s y n c ( i n : S t r i n g ) : S t r i n g s u s p e n d f u n a s y n c ( i n : S t r i n g ) : S t r i n g f u n s t r e a m ( i n : F l o w < S t r i n g > ) : S t r i n g f u n s t a t e S t r e a m ( i n : S t a t e F l o w < S t r i n g > ) :S t r i n g
  94. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    internal object Adapter : ZiplineAdapter<SomeSerice> { } }
  95. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    internal object Adapter : ZiplineAdapter<SomeSerice> { override fun outboundService(handler: Handler) } }
  96. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    internal object Adapter : ZiplineAdapter<SomeSerice> { override fun outboundService(handler: Handler) = object : SomeService { override fun userTypes(in: Payment) = handler.call(this, in) as RenderedPayment } } }
  97. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    internal object Adapter : ZiplineAdapter<SomeSerice> { override fun outboundService(handler: Handler) = object : SomeService { override fun userTypes(in: Payment) = handler.call(this, in) as RenderedPayment } } }
  98. interface SomeService : ZiplineService { … fun userTypes(in: Payment): RenderedPayment

    internal object Adapter : ZiplineAdapter<SomeSerice> { … } } @Serializable class RenderedPayment(…) @Serializable class Payment(…)
  99. QuickJS function sayHello() { return ['Hello', 'JavaScript'] .join(' '); }

    hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello" push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1
  100. QuickJS hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value

    "Hello" push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 "Hello, JavaScript"
  101. QuickJS hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value

    "Hello" push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } "Hello, JavaScript" hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello" push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1
  102. hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } "Hello, JavaScript" hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello" push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 Server: Client:
  103. hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") }
  104. hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") }
  105. hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") } {"version":3,"sources": ["../../src/commonMain/ kotlin/com/example/ Hello.kt"],"sourcesCont ent":[null],,"names": [],"mappings":";IAsBE,g C;IAMA,wB;IAMA,4B;"}
  106. hello.js:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") } {"version":3,"sources": ["../../src/commonMain/ kotlin/com/example/ Hello.kt"],"sourcesCont ent":[null],,"names": [],"mappings":";IAsBE,g C;IAMA,wB;IAMA,4B;"}
  107. Hello.kt:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") } {"version":3,"sources": ["../../src/commonMain/ kotlin/com/example/ Hello.kt"],"sourcesCont ent":[null],,"names": [],"mappings":";IAsBE,g C;IAMA,wB;IAMA,4B;"}
  108. Hello.kt:1: function: sayHello mode: strict stack_size: 3 opcodes: push_atom_value "Hello"

    push_atom_value "JavaScript" array_from 2 get_field2 join push_atom_value " " tail_call_method 1 function sayHello() { return ['Hello', 'JavaScript'] .join(' '); } fun sayHello() { return arrayOf("Hello", "JavaScript") .asDynamic() .join(" ") } {"version":3,"sources": ["../../src/commonMain/ kotlin/com/example/ Hello.kt"],"sourcesCont ent":[null],,"names": [],"mappings":";IAsBE,g C;IAMA,wB;IAMA,4B;"}
  109. java.lang.Exception: unexpected timezone: America/Gotham at captureStack (runtime/coreRuntime.kt:86) at IllegalStateException_init (kotlin-kotlin-stdlib-js-ir.js)

    at <anonymous> (app.cash.worldclock/TimeFormatter.kt:98) at <anonymous> (app.cash.worldclock/WorldClockJs.kt:615) at <anonymous> (InboundService.kt:837) at <anonymous> (zipline-root-zipline.js) at app.cash.zipline.OutboundService.runJob(platform.kt:37) at app.cash.zipline.EventLoop.run(CoroutineEventLoop.kt:57) at j.u.concurrent.Executor.runWorker(ThreadPoolExecutor.java:1167) at j.u.concurrent.Executor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:920)
  110. java.lang.Exception: unexpected timezone: America/Gotham at captureStack (runtime/coreRuntime.kt:86) at IllegalStateException_init (kotlin-kotlin-stdlib-js-ir.js)

    at <anonymous> (app.cash.worldclock/TimeFormatter.kt:98) at <anonymous> (app.cash.worldclock/WorldClockJs.kt:615) at <anonymous> (InboundService.kt:837) at <anonymous> (zipline-root-zipline.js) at app.cash.zipline.OutboundService.runJob(platform.kt:37) at app.cash.zipline.EventLoop.run(CoroutineEventLoop.kt:57) at j.u.concurrent.Executor.runWorker(ThreadPoolExecutor.java:1167) at j.u.concurrent.Executor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:920)
  111. Treehouse val root = findViewById<ViewGroup>(android.R.id.content)!! val composition = RedwoodComposition( scope

    = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) composition.setContent { Counter() } Treehouse Counter Sample
  112. Treehouse Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) composition.setContent { Counter() }
  113. Treehouse Counter Sample val root = findViewById<ViewGroup>(android.R.id.content)!! val composition =

    RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) composition.setContent { Counter() }
  114. Treehouse Counter Sample ) val root = findViewById<ViewGroup>(android.R.id.content)!! val composition

    = RedwoodComposition( scope = mainScope, container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) composition.setContent { Counter() } val = Redwood (
  115. val composition = RedwoodComposition( scope = mainScope, ) composition.setContent {

    Counter() } val root = findViewById<ViewGroup>(android.R.id.content)!! val rendering = RedwoodRendering( container = ViewGroupChildren(root), provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ), ) Presenter Compose Widgets Platform UI Zipline
  116. val composition = RedwoodComposition( scope = mainScope, ) composition.setContent {

    Counter() } Widgets Zipline Presenter Platform UI Compose val root = findViewById<ViewGroup>(android.R.id.content)!! val container = ViewGroupChildren(root) val provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ) val rendering = RedwoodRendering( container = container, provider = provider, )
  117. val composition = RedwoodComposition( scope = mainScope, ) composition.setContent {

    Counter() } Widgets Zipline Presenter Platform UI Compose val root = findViewById<ViewGroup>(android.R.id.content)!! val container = ViewGroupChildren(root) val provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ) val rendering = RedwoodRendering( container = container, provider = provider, )
  118. val composition = RedwoodComposition( scope = mainScope, ) composition.setContent {

    Counter() } Widgets Zipline Presenter Platform UI Compose val root = findViewById<ViewGroup>(android.R.id.content)!! val container = ViewGroupChildren(root) val provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ) val rendering = RedwoodRendering( container = container, provider = provider, )
  119. val composition = RedwoodComposition( scope = mainScope, ) composition.setContent {

    Counter() } Widgets kotlinx serialization Zipline Presenter Platform UI Compose kotlinx serialization val root = findViewById<ViewGroup>(android.R.id.content)!! val container = ViewGroupChildren(root) val provider = SchemaWidgetFactories( Counter = AndroidCounterWidgetFactory(this), RedwoodLayout = ViewRedwoodLayoutWidgetFactory(this), ) val rendering = RedwoodRendering( container = container, provider = provider, )
  120. Redwood Protocol data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  121. Redwood Protocol @Widget(1) data class Column( val children: () ->

    Unit, ) @Widget(2) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  122. Redwood Protocol @Widget(1) data class Column( val children: () ->

    Unit, ) @Widget(2) data class TextInput( @Property(1) val hint: String, @Property(2) val text: String, @Property(3) val onTextChanged: (String) -> Unit, )
  123. Redwood Protocol @Widget(1) data class Column( @Children(1) val children: ()

    -> Unit, ) @Widget(2) data class TextInput( @Property(1) val hint: String, @Property(2) val text: String, @Property(3) val onTextChanged: (String) -> Unit, )