Share
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: https://jakewharton.com/native-ui-with-multiplatform-compose/
Jake Wharton PRO
Transcript
195 slides
-
Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •
Powered by Compose -
Redwood • Kotlin multiplatform (Android, JVM, JS, iOS, MacOS) •
Powered by Compose • Shared presenter logic across all platforms -
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 -
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 -
2012 2013 2014 September October November Dece • Native UI
feels best • Design system is cross platform -
2012 2013 2014 September October November Dece • Native UI
feels best • Design system is cross platform • Hire the best engineers on each platform -
2014 2015 2016 2017 2018 ay June July August September
October November Dece • Code sharing is awesome -
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 -
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) -
ay June July August September October November Dece 2016 2017
2018 2019 2020 • Investing in Kotlin/MPP as the future -
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 -
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) -
2016 2017 2018 2019 2020 ay June July August September
October November Dece • Internal FormBlocker library -
2016 2017 2018 2019 2020 ay June July August September
October November Dece • Internal FormBlocker library • Form-like UI spec implemented on each platform -
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) -
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 -
2018 2019 2020 2021 2022 ay June July August September
October November Dece • FormBlocker 2.0? -
2018 2019 2020 2021 2022 ay June July August September
October November Dece • FormBlocker 2.0? • Share more than just protos with Kotlin mulitiplatform -
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 -
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 -
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 -
Figma Android Eng iOS Eng Web Eng View Impl UIKit
Impl DOM Impl -
Figma Android Eng iOS Eng Web Eng View Impl UIKit
Impl DOM Impl Redwood -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
Redwood Schema data class Column( val children: () -> Unit,
val birthday: LocalDate, ) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
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, ) -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
Redwood Schema data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
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, ) -
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, ) -
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, ) -
Redwood Compose data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
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, ) { … } -
Redwood Compose @Composable fun Column( children: @Composable () -> Unit,
) { … } @Composable fun TextInput( hint: String, text: String, onTextChanged: (String) -> Unit, ) { … } -
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) } } } -
Redwood Widgets data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
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)?) } -
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)?) } -
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)?) } -
Redwood Widgets interface Column<T : Any> : Widget<T> { val
children: Widget.Children<T> } -
Redwood Widgets class ViewColumn : Column<View> interface Column<T : Any>
: Widget<T> { val children: Widget.Children<T> } -
Redwood Widgets class ViewColumn( override val value: LinearLayout, ) :
Column<View> interface Column<T : Any> : Widget<T> { val children: Widget.Children<T> } -
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> } -
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> } -
Redwood Widgets interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun
Column(): Column<T> … } -
Redwood Widgets class EmojiViewWidgetFactory : EmojiWidgetFactory<View> interface EmojiWidgetFactory<T : Any>
: Widget.Factory<T> { fun Column(): Column<T> … } -
Redwood Widgets class EmojiViewWidgetFactory( private val context: Context, ) :
EmojiWidgetFactory<View> interface EmojiWidgetFactory<T : Any> : Widget.Factory<T> { fun Column(): Column<T> … } -
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> … } -
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> … } -
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> … } -
@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, ) } } } } -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Image -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Spacer Image -
Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)
Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)
Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)
Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text -
Spacer Image Column @Composable fun MessageCard(…) { Row(…) { Image(…)
Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text Spacer -
Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {
Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {
Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row -
Text Spacer Surface Spacer Image Column @Composable fun MessageCard(…) {
Row(…) { Image(…) Spacer(…) Column { Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Text -
@Composable fun MessageCard(…) { Row(…) { Image(…) Spacer(…) Column {
Text(…) Spacer(…) Surface(…) { Text(…) } } } } Root Row Spacer Image Column Text Spacer Surface Text -
Root Row Spacer Image Column Text Spacer Surface Text FrameLayout
LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView -
Root Row Spacer Image Column Text Spacer Surface Text UIStackView
UIStackView UIView UIImageView UIStackView UITextView UIView UIView UITextView -
Root Row Spacer Image Column Text Spacer Surface Text <div>
<div> <div> <img> <div> <span> <div> <div> <span> -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { … } -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { ComposeNode<…>( factory = …, update = { … }, ) } -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<…>( factory = …, update = { … }, ) } -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<EmojiWidgetFactory<*>, …>( factory = …, update = { … }, ) } -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = …, update = { … }, ) } -
Redwood Compose & Widgets @Composable fun TextInput( hint: String, text:
String, onTextChanged: (String) -> Unit, ) { RedwoodComposeNode<EmojiWidgetFactory<*>, TextInput<*>>( factory = EmojiWidgetFactory<*>::TextInput, update = { … }, ) } -
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) } }, ) } -
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) } }, ) } -
Root Row Spacer Image Column Text Spacer Surface Text FrameLayout
LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView -
Root Row Spacer Image Column Text Spacer Surface Text FrameLayout
LinearLayout View ImageView LinearLayout TextView View FrameLayout TextView Text(msg.body, …) -
Root Row Spacer Image Column Text Spacer Surface Text FrameLayout
LinearLayout View ImageView LinearLayout View FrameLayout TextView TextView Text(msg.body, …) -
class RealWorldClockService : WorldClockService { override fun formatTime(location: Location): String
{ … } } interface WorldClockService : ZiplineService { fun formatTime(location: Location): String } WorldClockService -
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!")) -
interface : ZiplineService { fun formatTime(location: Location): String } val
worldClock = zipline.take<WorldClockService>() println(worldClock.formatTime(Location("Droidcon NYC!")) -
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()) -
Redwood Protocol data class Column( val children: () -> Unit,
) data class TextInput( val hint: String, val text: String, val onTextChanged: (String) -> Unit, ) -
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, ) -
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, ) -
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, ) -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Widget(1) data class Column( @Property(1) val padding: Int =
24, @Children(1) val children: () -> Unit, ) -
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, } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column { Text(text
= "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text( text = "Hello,", modifier = Modifier.align(CenterHorizontally), ) Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp))
{ Text( text = "Hello,", modifier = Modifier.align(CenterHorizontally), ) Text(text = name) } } -
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) } } -
Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable
ColumnScope.() -> Unit, ) { … } -
Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable
ColumnScope.() -> Unit, ) { … } -
Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable
ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } } -
Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable
ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } } -
Modifiers @Composable fun Column( modifier: Modifier = Modifier, content: @Composable
ColumnScope.() -> Unit, ) { … } interface ColumnScope { fun Modifier.align(alignment: Alignment.Horizontal) { … } } val CenterHorizontally: Horizontal = … -
Modifiers @Widget(1) data class Column( @Property(1) val padding: Padding =
Normal, @Children(1) val children: () -> Unit, ) { enum class Padding { Small, Normal, Large } } -
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 -
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 -
Modifiers @Composable public fun Column( children: @Composable () -> Unit,
) { … } object ColumnScope -
Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,
) { … } object ColumnScope -
Modifiers @Widget(1) data class Column(…) { … } object ColumnScope
public data class HorizontalAlignment( val align: Horizontal, ) -
Modifiers @Widget(1) data class Column(…) { … } object ColumnScope
@LayoutModifier(1) public data class HorizontalAlignment( val align: Horizontal, ) -
Modifiers @Widget(1) data class Column(…) { … } object ColumnScope
@LayoutModifier(1, ColumnScope::class) public data class HorizontalAlignment( val align: Horizontal, ) -
Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,
) { … } object ColumnScope -
Modifiers @Composable public fun Column( children: @Composable ColumnScope.() -> Unit,
) { … } object ColumnScope { fun LayoutModifier.horizontalAlignment( align: Horizontal, ): LayoutModifier { … } } -
Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)
{ Text(text = "Hello,") Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)
{ Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)
{ Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } } -
Modifiers @Composable private fun Greeting(name: String) { Column(padding = Large)
{ Text( text = "Hello,", layoutModifier = LayoutModifier.horizontalAlignment(Center), ) Text(text = name) } } -
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) } } -
Modifiers class AndroidText( override val value: TextView, ) : Text<View>
{ override fun text(text: String?) { value.text = text } } -
Modifiers class AndroidText( override val value: TextView, ) : Text<View>
{ override var layoutModifiers = LayoutModifier override fun text(text: String?) { value.text = text } } -
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 } } -
Multiple children @Widget(6) data class Toolbar( @Property(1) val title: String,
@Children(1) val start: () -> Unit, @Children(2) val end: () -> Unit, ) -
Multiple children @Composable fun Toolbar( title: String, start: @Composable ()
-> Unit, end: @Composable () -> Unit, ) { … } -
Multiple children @Composable fun Toolbar( title: String, start: @Composable ()
-> Unit, end: @Composable () -> Unit, ) { Row { start() Text(title) end() } } -
Multiple children @Composable fun Toolbar( title: String, start: @Composable ()
-> Unit, end: @Composable () -> Unit, ) { Row { start() Text(title) end() } } -
Multiple children RedwoodComposeNode<…> { factory = EmojiWidgetFactory<*>::Toolbar updater = {
set(title) { title(title) } } children = {} } @Composable fun Toolbar( title: String, start: @Composable () -> Unit, end: @Composable () -> Unit, ) { } -
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() } } } -
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() } } } } -
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 } } -
Root Toolbar FrameLayout LinearLayout LinearLayout TextView LinearLayout Text Image Image
ImageView TextView ImageView -
Lint API check @Widget(3) // Shipped in app v1.0 data
class Text( @Property(1) val text: String?, ) -
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?, ) -
Lint API check @Composable fun Prompt(title: String) { Column {
FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } } -
Lint API check @Composable fun Prompt(title: String) { Column {
FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } } ! ! -
Lint API check @Composable fun Prompt(title: String) { Column {
FancyText("Send money?") Row { Button("Send", onClick = { … }) Button("Cancel", onClick = { … }) } } } -
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 = { … }) } } } -
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 = { … }) } } } -
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 = { … }) } } } -
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, ) -
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, ) -
Optional basic row/column/box layout data class Column( val children: ()
-> Unit, ) data class Row( val children: () -> Unit, ) data class Box( val children: () -> Unit, ) -
What's next? • Lint API check • Optional basic row/column/box
layout • Optional paginated column -
Optional paginated column ScrollableColumn { for (emoji in emojis) {
Row { Image(url = emoji.url) Text(text = emoji.name) } } } -
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 -
What's next? • Lint API check • Optional basic row/column/box
layout • Optional paginated column • Testing API -
What's next? • Lint API check • Optional basic row/column/box
layout • Optional paginated column • Testing API • Developer tooling -
What's next? • Lint API check • Optional basic row/column/box
layout • Optional paginated column • Testing API • Developer tooling • Documentation "