Upgrade to PRO for Only $50/Yearโ€”Limited-Time Offer! ๐Ÿ”ฅ

Jetpack Compose: First Blood

Jetpack Compose: Firstย Blood

Avatar for Vladimir Ivanov

Vladimir Ivanov

June 20, 2019
Tweet

More Decks by Vladimir Ivanov

Other Decks in Programming

Transcript

  1. ABOUT ME > Vladimir Ivanov > Designing Mobile-centric solutions for

    living > Primary Skill: Android 2/64 โ€” <EPAM>
  2. ABOUT ME > Vladimir Ivanov > Designing Mobile-centric solutions for

    living > Primary Skill: Android > Certified Google Cloud Architect. 2/64 โ€” <EPAM>
  3. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) emergency_call_layout.setOnClickListener

    { onEmergencyCallPressed() } filter_recycler_view.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) val filterAdapter = FilterAdapter(arrayListOf("One", "Two", "Three"), null, this::onFilterSelected) filter_recycler_view.adapter = filterAdapter crisis_centers_list.layoutManager = LinearLayoutManager(context) viewModel.crisisCenters.observe(this, crisisCenterObserver) } 5/64 โ€” <EPAM>
  4. WHAT WE USUALLY DO? > XML > Fragment > Presenter/ViewModel

    > styles > attributes 6/64 โ€” <EPAM>
  5. MINUSES > Several places to right code > Hard to

    debug > Impossible to reuse 9/64 โ€” <EPAM>
  6. PROS > Almost single place to write code > Easy

    to test > Easy to scale 14/64 โ€” <EPAM>
  7. PROS > Almost single place to write code > Easy

    to test > Easy to scale > Unbundled implementation 14/64 โ€” <EPAM>
  8. > React > Angular > Vue.js > SwiftUI > And

    now: Jetpack Compose 16/64 โ€” <EPAM>
  9. GENERALLY SPEAKING > Node for piece of UI, having children

    > function to render a root Node 19/64 โ€” <EPAM>
  10. GENERALLY SPEAKING > Node for piece of UI, having children

    > function to render a root Node > functions to render primitives: Text, Image, etc. 19/64 โ€” <EPAM>
  11. THEN... > we need to pass root Node down the

    branches 20/64 โ€” <EPAM>
  12. THEN... > we need to pass root Node down the

    branches > Which can be kinda harsh 20/64 โ€” <EPAM>
  13. SO... interface Composer { // add node as a child

    to the current Node, execute // `content` with `node` as the current Node fun emit(node: Node, content: () -> Unit = {}) } 21/64 โ€” <EPAM>
  14. THUS > ComposerImpl > top-level compose function > call top

    level render function with compose invocation 22/64 โ€” <EPAM>
  15. All this can be hidden with the help of compiler

    and runtime which Jetpack Compose happens to be 23/64 โ€” <EPAM>
  16. > How to start: d.android.com/jetpack/compose1 > How to start properly:

    Primer by raywenderlich.com2 2 https://www.raywenderlich.com/3604589-jetpack-compose-primer 1 https://developer.android.com/jetpack/compose 24/64 โ€” <EPAM>
  17. START WITH override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent {

    CraneWrapper { MaterialTheme { EditAndSlider() } } } } 25/64 โ€” <EPAM>
  18. > Looks like DSL > Looks like a tree >

    Every record is a component 26/64 โ€” <EPAM>
  19. COMPOSABLE COMPONENT > returns another function invocation > has @Composable

    > basically yields piece of UI 31/64 โ€” <EPAM>
  20. WHY COMPILER IS INVOLVED? > Every @Composable should accept a

    Composer as a last argument 32/64 โ€” <EPAM>
  21. WHY COMPILER IS INVOLVED? > Every @Composable should accept a

    Composer as a last argument > Think of @Composable as a keyword(like suspend) 32/64 โ€” <EPAM>
  22. Text( text = "Enter your login, ", style = TextStyle(fontSize

    = 120f), textAlign = TextAlign.Center ) 36/64 โ€” <EPAM>
  23. val progressState = +state { EditorState(text = "[email protected]") } EditableText(

    value = progressState.value, editorStyle = EditorStyle(TextStyle(fontSize = 48.toFloat())), onValueChange = { progressState.value = it } ) 37/64 โ€” <EPAM>
  24. val progressState = +state { EditorState(text = "[email protected]") } EditableText(

    value = progressState.value, editorStyle = EditorStyle(TextStyle(fontSize = 48.toFloat())), onValueChange = { progressState.value = it } ) 38/64 โ€” <EPAM>
  25. @Composable fun InputWithCaption(caption: String) { val labelStyle = +themeTextStyle {

    h6 } val progressState = +state { EditorState(text = "[email protected]") } Column(crossAxisAlignment = CrossAxisAlignment.Start) { Text(caption, style = labelStyle) Surface(color = customColor3) { EditableText( value = progressState.value, editorStyle = EditorStyle(TextStyle(fontSize = 48.toFloat())), onValueChange = { progressState.value = it } ) } } } 41/64 โ€” <EPAM>
  26. @Composable fun InputWithCaption(caption: String) { val labelStyle = +themeTextStyle {

    h6 } val progressState = +state { EditorState(text = "[email protected]") } Column(crossAxisAlignment = CrossAxisAlignment.Start) { Text(caption, style = labelStyle) Surface(color = customColor3) { EditableText( value = progressState.value, editorStyle = EditorStyle(TextStyle(fontSize = 48.toFloat())), onValueChange = { progressState.value = it } ) } } } 42/64 โ€” <EPAM>
  27. @Composable fun InputWithCaption(caption: String) { val labelStyle = +themeTextStyle {

    h6 } val progressState = +state { EditorState(text = "[email protected]") } Column(crossAxisAlignment = CrossAxisAlignment.Start) { Text(caption, style = labelStyle) Surface(color = customColor3) { EditableText( value = progressState.value, editorStyle = EditorStyle(TextStyle(fontSize = 48.toFloat())), onValueChange = { progressState.value = it } ) } } } 43/64 โ€” <EPAM>
  28. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CraneWrapper {

    MaterialTheme { Column { InputWithCaption("Login") InputWithCaption("Password") } } } } } 44/64 โ€” <EPAM>
  29. APPLY KOTLIN CONSTRUCTIONS setContent { CraneWrapper { MaterialTheme { Column

    { for (1..10) { InputWithCaption("$it") } } } } } 45/64 โ€” <EPAM>
  30. PASS LAMBDAS fun onSubmit() { Toast.makeText(this, "Hello!", LENGTH_LONG).show() } setContent

    { CraneWrapper { MaterialTheme { InputWithCaption("Login", onSubmit) } } } 46/64 โ€” <EPAM>
  31. REUSE STYLES val h6: TextStyle = TextStyle( fontFamily = FontFamily("Roboto"),

    fontWeight = FontWeight.w500, fontSize = 20f), 47/64 โ€” <EPAM>
  32. fun testRendersTheTitleAsPartOfTheRallyAppBarComponent() { val title = "Any title" val component

    = RallyApp(title) assertEquals(title, component.title.text) } fun testRendersTheRallyAppBarComponent() { val component = RallyApp(title ) component.matchWithSnapshot() } @Composable fun RallyAppBar(title: String) { Row { Text(text = title, style = +themeTextStyle { h4 }) } } 52/64 โ€” <EPAM>
  33. HOWEVER IMO this is a bit of a jump in

    logic. The assumption here is that asserting on the returned โ€œvirtual DOMโ€ of components is the right and only way to test those components. I disagree on both counts. With the right APIs exposed, Compose will be as testable as anything else and we will push you into the โ€œpit of successโ€ of testing the public API of components instead of their implementation details. -- Leland Richardson 53/64 โ€” <EPAM>
  34. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it 55/64 โ€” <EPAM>
  35. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely 55/64 โ€” <EPAM>
  36. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely > if you have a Frame object in current thread 55/64 โ€” <EPAM>
  37. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely > if you have a Frame object in current thread > Frame exists in UI thread 55/64 โ€” <EPAM>
  38. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely > if you have a Frame object in current thread > Frame exists in UI thread > Frames are in-memory observable non-durable transactions 55/64 โ€” <EPAM>
  39. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely > if you have a Frame object in current thread > Frame exists in UI thread > Frames are in-memory observable non-durable transactions > Frame to be renamed to Transaction 55/64 โ€” <EPAM>
  40. MULTITHREADING > Would be awesome to accept suspend functions, but

    current Kotlin-compose compiler plugin doesn't support it > You're possible to change the +state object from any thread safely > if you have a Frame object in current thread > Frame exists in UI thread > Frames are in-memory observable non-durable transactions > Frame to be renamed to Transaction > Batches already supported 55/64 โ€” <EPAM>
  41. FEEDBACK LOOP > No preview > Hot reloading is warm

    at the moment(~30 seconds on my machine) 56/64 โ€” <EPAM>
  42. FEEDBACK LOOP > No preview > Hot reloading is warm

    at the moment(~30 seconds on my machine) > Faster, tenant hot reloading to come 56/64 โ€” <EPAM>
  43. @Composable fun UserProfile(userId: Int) { val user = +state<User?>(userId) {

    null } +onCommit(userId) { val cancellationToken = UserAPI.get(userId) { user.value = it } onDispose { UserAPI.cancel(cancellationToken) } } if (user == null) { Loading() return } Text(text=user.name) Image(src=user.photo) } 60/64 โ€” <EPAM>
  44. COMPATIBILITY > You can use @Composable functions as views >

    You can use Views inside @Composable 61/64 โ€” <EPAM>