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

Jetpack Compose: exploring a pre-alpha

Jetpack Compose: exploring a pre-alpha

Slides for my presentation at Mobile Unplugged (https://mobileunplugged.com/) 2019

Luca Nicoletti

November 06, 2019
Tweet

More Decks by Luca Nicoletti

Other Decks in Programming

Transcript

  1. luca_nicolett JETPACK COMPOSE ๏ Luca Nicoletti ๏ Italian ๏ Android

    Engineer ๏ Based in London ๏ Babylon Health
  2. luca_nicolett JETPACK COMPOSE Google I/O
 May 2019 2-3 weeks 


    after Generic blogposts
 about all the things
 announced
  3. luca_nicolett JETPACK COMPOSE Google I/O
 May 2019 2-3 weeks 


    after Generic blogposts
 about all the things
 announced Generic blogpost
 about what is 
 Jetpack Compose
  4. luca_nicolett JETPACK COMPOSE Google I/O
 May 2019 2-3 weeks 


    after Generic blogposts
 about all the things
 announced Generic blogpost
 about what is 
 Jetpack Compose Specific blogpost
 about Jetpack
 Compose
  5. luca_nicolett JETPACK COMPOSE Google I/O
 May 2019 2-3 weeks 


    after Generic blogposts
 about all the things
 announced Generic blogpost
 about what is 
 Jetpack Compose Specific blogpost
 about Jetpack
 Compose End of the excitement
 around Jetpack 
 Compose
  6. luca_nicolett JETPACK COMPOSE Android Dev 
 Summit 
 Oct 2019

    2-3 weeks 
 after Some conversations on the kotlinlang slack channel
  7. luca_nicolett Declarative → describe what you would like Imperative →

    describe how to achieve it JETPACK COMPOSE Declarative
 A tower of 3 blocks. Imperative
 1. Put down first block
 2. Put down second block
 3. Put down third block
  8. luca_nicolett <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_gravity="center_horizontal"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="First

    block" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Second block" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Third block" /> </LinearLayout> ANDROID - OLD
  9. luca_nicolett What’s that? ๏ Declarative UI ๏ Concise & idiomatic

    ๏ Components ๏ Recomposition ๏ Compatible JETPACK COMPOSE
  10. luca_nicolett @GenerateView @Composable fun Greetings(name: String) { /* ... */

    } <GreetingsView android:id="@+id/greetings" app:name="Luca" /> val greetingsView = findViewById<GreetingsView>(R.id.greetings) greetingsView.name = "Luca" JETPACK COMPOSE
  11. luca_nicolett @GenerateView @Composable fun Greetings(name: String) { /* ... */

    } <GreetingsView android:id="@+id/greetings" app:name="Luca" /> val greetingsView = findViewById<GreetingsView>(R.id.greetings) greetingsView.name = "Luca" JETPACK COMPOSE
  12. luca_nicolett @GenerateView @Composable fun Greetings(name: String) { /* ... */

    } <GreetingsView android:id="@+id/greetings" app:name="Luca" /> val greetingsView = findViewById<GreetingsView>(R.id.greetings) greetingsView.name = "Luca" JETPACK COMPOSE
  13. luca_nicolett @GenerateView @Composable fun Greetings(name: String) { /* ... */

    } <GreetingsView android:id="@+id/greetings" app:name="Luca" /> val greetingsView = findViewById<GreetingsView>(R.id.greetings) greetingsView.name = "Luca" JETPACK COMPOSE
  14. luca_nicolett What’s that? ๏ Declarative UI ๏ Concise & idiomatic

    ๏ Components ๏ Recomposition ๏ Compatible ๏ Unbundled from the OS JETPACK COMPOSE
  15. luca_nicolett buildFeatures { // Enables Jetpack Compose for this module

    compose true } // Set both the Java and Kotlin compilers to target Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } JETPACK COMPOSE
  16. luca_nicolett dependencies { // Include the following Compose toolkit dependencies.

    implementation "androidx.ui:ui-framework:0.1.0-dev02" implementation "androidx.ui:ui-tooling:0.1.0-dev02" implementation "androidx.ui:ui-layout:0.1.0-dev02" implementation "androidx.ui:ui-material:0.1.0-dev02" } JETPACK COMPOSE
  17. luca_nicolett mkdir androidx-master-dev cd androidx-master-dev repo init -u https://android.googlesource.com/platform/manifest -b

    androidx-master-dev repo sync -j8 -c Download the code (and grab a coffee while we pull down 6GB) JETPACK COMPOSE
  18. luca_nicolett class MyActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContent { MyComposableFunction() } } } JETPACK COMPOSE
  19. luca_nicolett class MyActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContent { MyComposableFunction() } } } JETPACK COMPOSE
  20. luca_nicolett fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? {

    val craneView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext JETPACK COMPOSE
  21. luca_nicolett fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? {

    val craneView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext JETPACK COMPOSE
  22. luca_nicolett fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? {

    val craneView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext JETPACK COMPOSE
  23. luca_nicolett : CompositionContext? { val craneView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0)

    as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext ) { content() } } } JETPACK COMPOSE
  24. luca_nicolett : CompositionContext? { val craneView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0)

    as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext ) { content() } } } JETPACK COMPOSE
  25. luca_nicolett @Composable fun Greetings(text: String) { Text("Hello $text") } @Preview

    @Composable fun GreetMobileUnplugged() { Greetings(text = “Mobile Unplugged”) } JETPACK COMPOSE
  26. luca_nicolett /** * A [FileEditor] that displays a preview of

    composable * elements defined in the given [psiFile]. * * The editor will display previews for all declared * `@Composable` functions that also use the `@Preview` * (see [PREVIEW_ANNOTATION_FQN])annotation. * For every preview element a small XML is generated * that allows Layoutlib to render a `@Composable` functions. * * @param psiFile [PsiFile] pointing to the Kotlin source * containing the code to preview. * @param previewProvider call to obtain the [PreviewElement]s * from the file. */ JETPACK COMPOSE
  27. luca_nicolett @Composable fun Text(/* ... */) { /* ... */

    Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) // Paints the text onto the given canvas. } /* ... */ } JETPACK COMPOSE
  28. luca_nicolett @Composable fun Text(/* ... */) { /* ... */

    Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) // Paints the text onto the given canvas. } /* ... */ } JETPACK COMPOSE
  29. luca_nicolett @Composable fun Text(/* ... */) { /* ... */

    Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) // Paints the text onto the given canvas. } /* ... */ } JETPACK COMPOSE
  30. luca_nicolett @Composable fun TextField(/* ... */) { // States val

    hasFocus = +state { false } val coords = +state<LayoutCoordinates?> { null } val inputSession = +state { NO_SESSION } /* ... */ } JETPACK COMPOSE
  31. luca_nicolett /** * The State class is an @Model class

    meant to * wrap around a single value. * It is used in the `+state` and `+stateFor` effects. */ @Model class State<T> @PublishedApi internal constructor(value: T) : Framed {} JETPACK COMPOSE
  32. luca_nicolett @Composable fun Counter() { var count by +state {

    0 } Text(text = "You clicked $count times") Button("Click me",{ count = count + 1 }) } JETPACK COMPOSE
  33. luca_nicolett @Composable fun Counter() { var count by +state {

    0 } Text(text = "You clicked $count times") Button("Click me",{ count = count + 1 }) } JETPACK COMPOSE
  34. luca_nicolett @Composable fun Counter() { var count by +state {

    0 } Text(text = "You clicked $count times") Button("Click me",{ count = count + 1 }) } JETPACK COMPOSE
  35. luca_nicolett @Composable fun Counter() { var count by +state {

    0 } Text(text = "You clicked $count times") Button("Click me",{ count = count + 1 }) } JETPACK COMPOSE
  36. luca_nicolett /** * [Model] can be applied to a class

    which represents your * application's data model, and will cause instances of the * class to become observable, such that a read of a property * of an instance of this class during the invocation of a * composable function will cause that component to be * "subscribed" to mutations of that instance. Composable * functions which directly or indirectly read properties * of the model class, the composables will be recomposed * whenever any properties of the the model are written to. */ JETPACK COMPOSE
  37. luca_nicolett @Model data class TaskModel( var isDone: Boolean, val description:

    String ): BaseModel() TaskModel.kt: (14, 3): Model objects do not support inheritance JETPACK COMPOSE
  38. luca_nicolett RawDragGestureDetector(DragObserver { distance -> //update animation value }) {

    PressGestureDetector({ position -> // animate to position }) { Container(60.dp, expanded = true) { DrawSeekBar(animValue.value) } } } JETPACK COMPOSE
  39. luca_nicolett RawDragGestureDetector(DragObserver { distance -> //update animation value }) {

    PressGestureDetector({ position -> // animate to position }) { Container(60.dp, expanded = true) { DrawSeekBar(animValue.value) } } } JETPACK COMPOSE
  40. luca_nicolett RawDragGestureDetector(DragObserver { distance -> //update animation value }) {

    PressGestureDetector({ position -> // animate to position }) { Container(60.dp, expanded = true) { DrawSeekBar(animValue.value) } } } JETPACK COMPOSE
  41. luca_nicolett RawDragGestureDetector(DragObserver { distance -> //update animation value }) {

    PressGestureDetector({ position -> // animate to position }) { Container(60.dp, expanded = true) { DrawSeekBar(animValue.value) } } } JETPACK COMPOSE
  42. luca_nicolett @Composable fun DrawSeekBar(x: Float) { var paint = +memo

    { Paint() } Draw { canvas, parentSize -> /* ... */ canvas.drawRect(Rect(/* ... */), paint) canvas.drawRect(Rect(/* ... */), paint) canvas.drawCircle(/* ... */, paint) } } JETPACK COMPOSE
  43. luca_nicolett @Composable fun DrawSeekBar(x: Float) { var paint = +memo

    { Paint() } Draw { canvas, parentSize -> /* ... */ canvas.drawRect(Rect(/* ... */), paint) canvas.drawRect(Rect(/* ... */), paint) canvas.drawCircle(/* ... */, paint) } } JETPACK COMPOSE
  44. luca_nicolett JETPACK COMPOSE @Composable fun RippleRect() { val radius =

    withDensity(+ambientDensity()) { TargetRadius.toPx() } val toState = +state { ButtonStatus.Initial } val rippleTransDef = +memo { createTransDef(radius.value) } val onPress: (PxPosition) -> Unit = { p -> down.x = p.x.value down.y = p.y.value toState.value = ButtonStatus.Pressed }
  45. luca_nicolett TargetRadius.toPx() } val toState = +state { ButtonStatus.Initial }

    val rippleTransDef = +memo { createTransDef(radius.value) } val onPress: (PxPosition) -> Unit = { p -> down.x = p.x.value down.y = p.y.value toState.value = ButtonStatus.Pressed } val onRelease: () -> Unit = { toState.value = ButtonStatus.Released } PressGestureDetector(onPress, onRelease) { Container(true) { Transition( JETPACK COMPOSE
  46. luca_nicolett JETPACK COMPOSE down.y = p.y.value toState.value = ButtonStatus.Pressed }

    val onRelease: () -> Unit = { toState.value = ButtonStatus.Released } PressGestureDetector(onPress, onRelease) { Container(true) { Transition( definition = rippleTransDef, toState = toState.value ) { state -> RippleRectFromState(state = state) } } } }
  47. luca_nicolett JETPACK COMPOSE down.y = p.y.value toState.value = ButtonStatus.Pressed }

    val onRelease: () -> Unit = { toState.value = ButtonStatus.Released } PressGestureDetector(onPress, onRelease) { Container(true) { Transition( definition = rippleTransDef, toState = toState.value ) { state -> RippleRectFromState(state = state) } } } }
  48. luca_nicolett JETPACK COMPOSE @Composable fun RippleRectFromState(state: TransitionState) { // TODO:

    file bug for when "down" is not a // file level val, it's not memoized correctly val x = down.x val y = down.y val paint = Paint().apply { color = Color( alpha = getAlpha(), red = 0, green = 235,
  49. luca_nicolett JETPACK COMPOSE // file level val, it's not memoized

    correctly val x = down.x val y = down.y val paint = Paint().apply { color = Color( alpha = getAlpha(), red = 0, green = 235, blue = 224 ) } val radius = state[radius] Draw { canvas, _ ->
  50. luca_nicolett JETPACK COMPOSE red = 0, green = 235, blue

    = 224 ) } val radius = state[radius] Draw { canvas, _ -> canvas.drawCircle( Offset(x, y), radius, paint ) } }
  51. luca_nicolett JETPACK COMPOSE @Composable fun AlertDialog( onCloseRequest: () -> Unit,

    title: (@Composable() () -> Unit)? = null, text: (@Composable() () -> Unit), buttons: @Composable() () -> Unit ) { val currentColors = +ambient(Colors) val currentTypography = +ambient(Typography) Dialog(onCloseRequest = onCloseRequest) { /* ... */ } }
  52. luca_nicolett JETPACK COMPOSE @Composable fun Dialog( onCloseRequest: () -> Unit,

    children: @Composable() () -> Unit ) { val context = +ambient(ContextAmbient) val dialog = +memo { DialogWrapper(context, onCloseRequest) } +onActive { dialog.show()
  53. luca_nicolett JETPACK COMPOSE @Composable fun Dialog( onCloseRequest: () -> Unit,

    children: @Composable() () -> Unit ) { val context = +ambient(ContextAmbient) val dialog = +memo { DialogWrapper(context, onCloseRequest) } +onActive { dialog.show()
  54. luca_nicolett JETPACK COMPOSE ) { val context = +ambient(ContextAmbient) val

    dialog = +memo { DialogWrapper(context, onCloseRequest) } +onActive { dialog.show() onDispose { dialog.dismiss() dialog.disposeComposition() } } +onCommit { dialog.setContent(children)
  55. luca_nicolett JETPACK COMPOSE ) { val context = +ambient(ContextAmbient) val

    dialog = +memo { DialogWrapper(context, onCloseRequest) } +onActive { dialog.show() onDispose { dialog.dismiss() dialog.disposeComposition() } } +onCommit { dialog.setContent(children)
  56. luca_nicolett JETPACK COMPOSE ) { val context = +ambient(ContextAmbient) val

    dialog = +memo { DialogWrapper(context, onCloseRequest) } +onActive { dialog.show() onDispose { dialog.dismiss() dialog.disposeComposition() } } +onCommit { dialog.setContent(children)
  57. luca_nicolett data class PaddingModifier( val left: IntPx = 0.ipx, val

    top: IntPx = 0.ipx, val right: IntPx = 0.ipx, val bottom: IntPx = 0.ipx ) : LayoutModifier { /* ... */ } JETPACK COMPOSE
  58. luca_nicolett /** * An immutable chain of [modifier elements][Modifier.Element] *

    for use with Composables. A Composable that has a `Modifier` * can be considered decorated or wrapped by that `Modifier`. * * Composables that accept a [Modifier] as a parameter to be * applied to the whole component represented by the composable * function should name the parameter `modifier` and assign the * parameter a default value of [Modifier.None] */ JETPACK COMPOSE
  59. luca_nicolett Text( text = "Hello ", modifier = PaddingModifier(16.dp) )

    JETPACK COMPOSE Padding(16.dp) { Text("Hello ") } VS
  60. luca_nicolett fun CustomTheme(body: @Composable() () -> Unit) { val colors

    = MaterialColors( primary = Color(0xFFDD0D3C), primaryVariant = Color(0xFFC20029), secondary = Color(0xFF00AD3C), /* other colors */ ) MaterialTheme (colors = colors) { body() } } JETPACK COMPOSE
  61. luca_nicolett override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CustomTheme

    { TopAppBar( title = { Text("Exploring Compose") } ) /* ... */ } } } JETPACK COMPOSE
  62. luca_nicolett <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowTranslucentStatus">true</item> <item name="android:statusBarColor">@android:color/white</item> <item

    name="android:windowTranslucentNavigation">true</item> <item name="android:fitsSystemWindows">true</item> </style> </resources> JETPACK COMPOSE
  63. luca_nicolett override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CustomTheme

    { Column { ColoredRect(Color.Transparent, height = 24.dp) TopAppBar(title = { Text("Exploring Compose") } } } } } JETPACK COMPOSE
  64. luca_nicolett override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CustomTheme

    { Column { ColoredRect(Color.Transparent, height = 24.dp) TopAppBar(title = { Text("Exploring Compose") } } } } } JETPACK COMPOSE
  65. luca_nicolett - https://android-developers.googleblog.com/2019/10/android-dev-summit-2019-keynote.html
 https://blog.karumi.com/android-jetpack-compose-review/ - https://blog.karumi.com/android-jetpack-compose-review/ - https://android.jlelse.eu/jetpack-compose-primer-92ff005b7ce2 - https://developer.android.com/jetpack/compose/tutorial

    - https://www.youtube.com/watch?v=VsStyq4Lzxo - https://medium.com/q42-engineering/android-jetpack-compose-895b7fd04bf4 - https://blog.karumi.com/android-jetpack-compose-review - https://courses.csail.mit.edu/6.831/2006/lectures/L9.pdf - http://intelligiblebabble.com/compose-from-first-principles/ - http://intelligiblebabble.com/content-on-declarative-ui/ - https://www.youtube.com/watch? v=Q9MtlmmN4Q0&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=60 - https://www.youtube.com/watch? v=SPsdRXcgqjI&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=8&t=0s - https://www.youtube.com/watch? v=XPMrnR1_Biw&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=13&t=0s RESOURCES