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

Native UI with Multiplatform Compose (Droidcon NYC 2022)

Native UI with Multiplatform Compose (Droidcon NYC 2022)

Compose is a delightful way to build UI in Kotlin, but Compose UI doesn't always make sense (or exist) on platforms like the web and iOS. React Native uses the native UI toolkit of each platform, but requires JavaScript and is always chasing compatibility with each platform's new features.

Redwood is a new tool from Cash App which lets you keep writing Kotlin and Compose but target the native UI toolkit of each platform.

This session will introduce Redwood by:
- Comparing and contrasting it with other cross-platform tools
- Demonstrate its differentiating features in samples and production usage
- Document the internal architecture of how it works
- Using it to update screens outside of the app store process

Video: Coming soon!

Jake Wharton
PRO

September 01, 2022
Tweet

More Decks by Jake Wharton

Other Decks in Technology

Transcript

  1. @jakewharton @jessewilson Native UI with Multiplatform Compose

  2. Demo

  3. Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS)

  4. Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •

    Powered by Compose
  5. Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •

    Powered by Compose • Shared presenter logic across all platforms
  6. Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •

    Powered by Compose • Shared presenter logic across all platforms • Uses native UI toolkit specific to each platform
  7. Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •

    Powered by Compose • Shared presenter logic across all platforms • Uses native UI toolkit specific to each platform • Not an all-or-nothing framework
  8. 2020 2021 2022 ay June July August September

  9. 2012 2013 2014 September October November Dece • Native UI

    feels best
  10. 2012 2013 2014 September October November Dece • Native UI

    feels best • Design system is cross platform
  11. 2012 2013 2014 September October November Dece • Native UI

    feels best • Design system is cross platform • Hire the best engineers on each platform
  12. 2014 2015 2016 2017 2018 ay June July August September

    October November Dece • Code sharing is awesome
  13. 2014 2015 2016 2017 2018 ay June July August September

    October November Dece • Code sharing is awesome • Use JS engine to share logic which changes frequently
  14. 2014 2015 2016 2017 2018 ay June July August September

    October November Dece • Code sharing is awesome • Use JS engine to share logic which changes frequently • Duktape-Android library from Square (Droidcon talk)
  15. ay June July August September October November Dece 2016 2017

    2018 2019 2020 • Investing in Kotlin/MPP as the future
  16. ay June July August September October November Dece 2016 2017

    2018 2019 2020 • Investing in Kotlin/MPP as the future • Server starts driving more logic on the client
  17. ay June July August September October November Dece 2016 2017

    2018 2019 2020 • Investing in Kotlin/MPP as the future • Server starts driving more logic on the client • SQLDelight goes multiplatform (Droidcon talk)
  18. 2016 2017 2018 2019 2020 ay June July August September

    October November Dece • Internal FormBlocker library
  19. 2016 2017 2018 2019 2020 ay June July August September

    October November Dece • Internal FormBlocker library • Form-like UI spec implemented on each platform
  20. 2016 2017 2018 2019 2020 ay June July August September

    October November Dece • Internal FormBlocker library • Form-like UI spec implemented on each platform • Web 1.0-like via protos (HTML) and native UI (browser)
  21. 2016 2017 2018 2019 2020 ay June July August September

    October November Dece • Internal FormBlocker library • Form-like UI spec implemented on each platform • Web 1.0-like via protos (HTML) and native UI (browser) • Logic & controls baked in app
  22. 2018 2019 2020 2021 2022 ay June July August September

    October November Dece • FormBlocker 2.0?
  23. 2018 2019 2020 2021 2022 ay June July August September

    October November Dece • FormBlocker 2.0? • Share more than just protos with Kotlin mulitiplatform
  24. 2018 2019 2020 2021 2022 ay June July August September

    October November Dece • FormBlocker 2.0? • Share more than just protos with Kotlin mulitiplatform • Web 2.0-like with rich, updatable logic
  25. 2018 2019 2020 2021 2022 ay June July August September

    October November Dece • FormBlocker 2.0? • Share more than just protos with Kotlin mulitiplatform • Web 2.0-like with rich, updatable logic • Not abandon our native engineers
  26. 2018 2019 2020 2021 2022 ay June July August September

    October November Dece • FormBlocker 2.0? • Share more than just protos with Kotlin mulitiplatform • Web 2.0-like with rich, updatable logic • Not abandon our native engineers • Evaluated the existing cross-platform solutions
  27. Figma Android Eng iOS Eng Web Eng

  28. Figma Android Eng iOS Eng Web Eng View Impl UIKit

    Impl DOM Impl
  29. Figma Android Eng iOS Eng Web Eng View Impl UIKit

    Impl DOM Impl Redwood
  30. Redwood Schema data class Column( val children: () -> Unit,

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

    val birthday: LocalDate, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  32. 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, )
  33. Redwood Schema data class Column( val children: () -> Unit,

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

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

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

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

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  38. 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, )
  39. 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, )
  40. 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, )
  41. Schema Widgets Compose

  42. Redwood Compose data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  43. 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, ) { … }
  44. Redwood Compose @Composable fun Column( children: @Composable () -> Unit,

    ) { … } @Composable fun TextInput( hint: String, text: String, onTextChanged: (String) -> Unit, ) { … }
  45. 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) } } }
  46. Redwood Widgets data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  47. 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)?) }
  48. 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)?) }
  49. 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)?) }
  50. Redwood Widgets interface Column<T : Any> : Widget<T> { val

    children: Widget.Children<T> }
  51. Redwood Widgets class ViewColumn : Column<View> interface Column<T : Any>

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

    Column<View> interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> }
  53. 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> }
  54. 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> }
  55. Redwood Widgets interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun

    Column(): Column<T> … }
  56. Redwood Widgets class EmojiViewWidgetFactory : EmojiWidgetFactory<View> interface EmojiWidgetFactory<T : Any>

    : Widget.Factory<T> { fun Column(): Column<T> … }
  57. Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :

    EmojiWidgetFactory<View> interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … }
  58. 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> … }
  59. 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> … }
  60. 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> … }
  61. @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, ) } } } }
  62. @Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  77. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

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

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

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

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

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

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

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

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

    String, onTextChanged: (String) -> Unit, ) {
 RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = EmojiWidgetFactory<*>::TextInput, update = { … }, ) }
  86. 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) } }, ) }
  87. 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) } }, ) }
  88. Root Row Spacer Image Column Text Spacer Surface Text FrameLayout

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

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

    LinearLayout View ImageView LinearLayout View FrameLayout TextView TextView Text(msg.body, …)
  91. Demo

  92. None
  93. class WorldClockService { fun formatTime(location: Location): String { … }

    }
  94. class RealWorldClockService : WorldClockService { override fun formatTime(location: Location): String

    { … } } interface WorldClockService : ZiplineService { fun formatTime(location: Location): String } WorldClockService
  95. class RealWorldClockService : WorldClockService { override fun formatTime(location: Location): String

    { … } } zipline.bind<WorldClockService>(RealWorldClockService()) interface : ZiplineService { fun formatTime(location: Location): String } val worldClock = zipline.take<WorldClockService>() println(worldClock.formatTime(Location("Droidcon NYC!"))
  96. interface : ZiplineService { fun formatTime(location: Location): String } val

    worldClock = zipline.take<WorldClockService>() println(worldClock.formatTime(Location("Droidcon NYC!"))
  97. interface : ZiplineService { fun formatTime(location: Location): String } val

    worldClock = zipline.take<WorldClockService>() println(worldClock.formatTime(Location("Droidcon NYC!")) class RealWorldClockService : WorldClockService { override fun formatTime(location: Location): String { Fancy new implementation… } } zipline.bind<WorldClockService>(RealWorldClockService())
  98. Schema Widgets Compose

  99. Schema Widgets Compose Compose Protocol Widget Protocol

  100. Widgets Widget Protocol Zipline Presenter Platform UI Compose Compose Protocol

  101. Widgets Widget Protocol Zipline Platform UI

  102. Widgets Widget Protocol Zipline Platform UI Presenter Compose Compose Protocol

  103. Redwood Protocol data class Column( val children: () -> Unit,

    ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  104. 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, )
  105. 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, )
  106. 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, )
  107. Redwood + Zipline ➡ Treehouse

  108. Widgets Widget Protocol Zipline Platform UI Presenter Compose Compose Protocol

  109. Treehouse Widgets Widget Protocol Zipline Platform UI Presenter Compose Compose

    Protocol
  110. Demo

  111. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  112. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  113. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  114. Modifiers @Widget(1) data class Column( @Children(1) val children: () ->

    Unit, )
  115. Modifiers @Widget(1) data class Column( @Property(1) val padding: Int =

    24, @Children(1) val children: () -> Unit, )
  116. Modifiers Int 24 @Widget(1) data class Column( @Property(1) val padding:

    Padding = Normal, @Children(1) val children: () -> Unit, ) { enum class Padding { Small, Normal, Large, } }
  117. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  118. Modifiers @Composable private fun Greeting(name: String) { Column { Text(text

    = "Hello,") Text(text = name) } }
  119. Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)

    { Text(text = "Hello,") Text(text = name) } }
  120. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  121. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text(text = "Hello,") Text(text = name) } }
  122. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text( text = "Hello,", modifier = Modifier.align(CenterHorizontally), ) Text(text = name) } }
  123. Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))

    { Text( text = "Hello,", modifier = Modifier.align(CenterHorizontally), ) Text(text = name) } }
  124. C o l u m n Modifiers @Composable private fun

    Greeting(name: String) { Row(modifier = Modifier.padding(24.dp)) { Text( text = "Hello,", modifier = Modifier.align(CenterHorizontally), ) Text(text = name) } }
  125. Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable

    ColumnScope.() -> Unit, ) { … }
  126. Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable

    ColumnScope.() -> Unit, ) { … }
  127. Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable

    ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } }
  128. Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable

    ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } }
  129. Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable

    ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } } val CenterHorizontally: Horizontal = …
  130. Modifiers @Widget(1) data class Column( @Property(1) val padding: Padding =

    Normal, @Children(1) val children: () -> Unit, ) { enum class Padding { Small, Normal, Large } }
  131. Modifiers @Widget(1) data class Column( @Property(1) val padding: Padding =

    Normal, @Children(1) val children: () -> Unit, ) { enum class Padding { Small, Normal, Large } } object ColumnScope
  132. Modifiers @Widget(1) data class Column( @Property(1) val padding: Padding =

    Normal, @Children(1) val children: ColumnScope.() -> Unit, ) { enum class Padding { Small, Normal, Large } } object ColumnScope
  133. Modifiers @Composable public fun Column( children: @Composable () -> Unit,

    ) { … }
  134. Modifiers @Composable public fun Column( children: @Composable () -> Unit,

    ) { … } object ColumnScope
  135. Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,

    ) { … } object ColumnScope
  136. Modifiers @Widget(1) data class Column(…) { … } object ColumnScope

  137. Modifiers @Widget(1) data class Column(…) { … } object ColumnScope

    public data class HorizontalAlignment( val align: Horizontal, )
  138. Modifiers @Widget(1) data class Column(…) { … } object ColumnScope

    @LayoutModifier(1) public data class HorizontalAlignment( val align: Horizontal, )
  139. Modifiers @Widget(1) data class Column(…) { … } object ColumnScope

    @LayoutModifier(1, ColumnScope::class) public data class HorizontalAlignment( val align: Horizontal, )
  140. Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,

    ) { … } object ColumnScope
  141. Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,

    ) { … } object ColumnScope { fun LayoutModifier.horizontalAlignment( align: Horizontal, ): LayoutModifier { … } }
  142. Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)

    { Text(text = "Hello,") Text(text = name) } }
  143. Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)

    { Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } }
  144. Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)

    { Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } }
  145. Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)

    { Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } }
  146. C o l u m n Modifiers @Composable private fun

    Greeting(name: String) { Row(padding = Large) { Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } }
  147. Modifiers class AndroidText( override val value: TextView, ) : Text<View>

    { override fun text(text: String?) { value.text = text } }
  148. Modifiers class AndroidText( override val value: TextView, ) : Text<View>

    { override var layoutModifiers = LayoutModifier override fun text(text: String?) { value.text = text } }
  149. Modifiers class AndroidText( override val value: TextView, ) : Text<View>

    { override var layoutModifiers = LayoutModifier set(layoutModifiers) { // Write data to value.layoutParams… field = layoutModifiers } override fun text(text: String?) { value.text = text } }
  150. Schema Widgets Compose Compose Protocol Widget Protocol

  151. Schema Widgets Compose Compose Protocol Widget Protocol Layout Modifiers

  152. Multiple children @Widget(1) data class Column( @Children(1) val children: ()

    -> Unit, )
  153. Multiple children

  154. Multiple children

  155. Multiple children @Widget(6) data class Toolbar( @Property(1) val title: String,

    )
  156. Multiple children @Widget(6) data class Toolbar( @Property(1) val title: String,

    @Children(1) val start: () -> Unit, @Children(2) val end: () -> Unit, )
  157. Multiple children @Composable fun Toolbar( title: String, start: @Composable ()

    -> Unit, end: @Composable () -> Unit, ) { … }
  158. Multiple children @Composable fun Toolbar( title: String, start: @Composable ()

    -> Unit, end: @Composable () -> Unit, ) { Row { start() Text(title) end() } }
  159. Multiple children @Composable fun Toolbar( title: String, start: @Composable ()

    -> Unit, end: @Composable () -> Unit, ) { Row { start() Text(title) end() } }
  160. Multiple children RedwoodComposeNode<…> { factory = EmojiWidgetFactory<*>::Toolbar updater = {

    set(title) { title(title) } } children = {} } @Composable fun Toolbar( title: String, start: @Composable () -> Unit, end: @Composable () -> Unit, ) { }
  161. Multiple children @Composable fun Toolbar( title: String, start: @Composable ()

    -> Unit, end: @Composable () -> Unit, ) { RedwoodComposeNode<…> { factory = EmojiWidgetFactory<*>::Toolbar updater = { set(title) { title(title) } } children = { start() end() } } }
  162. Multiple children @Composable fun Toolbar( title: String, start: @Composable ()

    -> Unit, end: @Composable () -> Unit, ) { RedwoodComposeNode<…> { factory = EmojiWidgetFactory<*>::Toolbar updater = { set(title) { title(title) } } children = { SyntheticChildren(1) { start() } SyntheticChildren(2) { end() } } } }
  163. Multiple children class AndroidToolbar( private val toolbarBinding: ToolbarBinding ) :

    Toolbar<View> { override val start = ViewGroupChildren(toolbarBinding.start) override val end = ViewGroupChildren(toolbarBinding.start) fun title(title: String) { toolbarBinding.title.text = title } }
  164. Root FrameLayout

  165. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout

  166. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image

  167. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image

  168. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image ImageView

  169. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image Image ImageView

  170. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image Image ImageView

  171. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Image Image ImageView

    ImageView
  172. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Text Image Image

    ImageView ImageView
  173. Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Text Image Image

    ImageView TextView ImageView
  174. What's next? • Lint API check

  175. Lint API check @Widget(3) // Shipped in app v1.0 data

    class Text( @Property(1) val text: String?, )
  176. Lint API check @Widget(3) // Shipped in app v1.0 data

    class Text( @Property(1) val text: String?, ) @Widget(4) // Shipped in app v1.2 data class FancyText( @Property(1) val text: String?, )
  177. Lint API check @Composable fun Prompt(title: String) { Column {

    FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } }
  178. Lint API check @Composable fun Prompt(title: String) { Column {

    FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } } ! !
  179. Lint API check @Composable fun Prompt(title: String) { Column {

    FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } }
  180. Lint API check @Composable fun Prompt(title: String) { Column {

    if (WidgetVersion >= VERSION_1_2) { FancyText("Send money?") } else { Text("Send money?") } Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } }
  181. Lint API check @Composable fun Prompt(title: String) { Column {

    if (WidgetVersion >= VERSION_1_2) { FancyText("Send money?") } else { Text("Send money?") } Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } }
  182. Lint API check @Composable fun Prompt(title: String) { Column {

    if (WidgetVersion >= VERSION_1_2) { FancyText("Send money?") } else { Text("Send money?") } Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } }
  183. What's next? • Lint API check • Optional basic row/column/box

    layout
  184. Optional basic row/column/box layout data class Column( val children: ()

    -> Unit, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  185. Optional basic row/column/box layout data class Column( val children: ()

    -> Unit, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, )
  186. Optional basic row/column/box layout data class Column( val children: ()

    -> Unit, ) data class Row( val children: () -> Unit, ) data class Box( val children: () -> Unit, )
  187. What's next? • Lint API check • Optional basic row/column/box

    layout • Optional paginated column
  188. Optional paginated column ScrollableColumn { for (emoji in emojis) {

    Row { Image(url = emoji.url) Text(text = emoji.name) } } }
  189. Optional paginated column LazyColumn { items(emojis) { Row { Image(url

    = emoji.url) Text(text = emoji.name) } } } S c r o l l a b l e f o r ( e m o j i i n
  190. What's next? • Lint API check • Optional basic row/column/box

    layout • Optional paginated column • Testing API
  191. What's next? • Lint API check • Optional basic row/column/box

    layout • Optional paginated column • Testing API • Developer tooling
  192. What's next? • Lint API check • Optional basic row/column/box

    layout • Optional paginated column • Testing API • Developer tooling • Documentation "
  193. Should you use it? Not yet (probably)

  194. .com/cashapp/redwood

  195. @jakewharton @jessewilson Native UI with Multiplatform Compose