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

Compose: Estados y Recomposición

Bruno Aybar
December 16, 2020

Compose: Estados y Recomposición

Exploraremos Jetpack Compose, y cómo los conceptos de States y Recomposition hacen posible que tengamos este nuevo paradigma de UI declarativa en Android.

Bruno Aybar

December 16, 2020
Tweet

More Decks by Bruno Aybar

Other Decks in Technology

Transcript

  1. @brunoaybarg Bruno125 Bruno Aybar Principal Engineer @ Avantica Android Dev

    Peru co-organizer brunoaybar.com/slides/compose-state-recomposition.pdf
  2. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } }
  3. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } } 1. Solución 100% orientada a Kotlin 2. UI declarativa, basada en @Composables 3. Composición en lugar de Herencia
  4. Activities + Compose class MainActivity: Activity() { override fun onCreate(...)

    { super.onCreate(...) setContent { AppTheme { MainContent() } } } }
  5. Usando Estados @Composable fun Counter() { val count = remember

    { mutableStateOf(0) } Button( onClick = { count.value += 1 }, content = { Text("value: ${count.value}") } ) }
  6. Usando Estados @Composable fun Counter() { val count = remember

    { mutableStateOf(0) } Button( onClick = { count.value += 1 }, content = { Text("value: ${count.value}") } ) }
  7. MutableState State interface MutableState<T> : State<T> { override var value:

    T operator fun component1(): T operator fun component2(): (T) -> Unit } interface State<T> { val value: T }
  8. @Composable fun Counter() { val count = remember { mutableStateOf(0)

    } Button( onClick = { count.value += 1 }, content = { Text("value: ${count.value}") } ) }
  9. Snapshot MutableState MutableState private class SnapshotMutableState<T>( value: T, val policy:

    SnapshotMutationPolicy<T> ) : StateObject, MutableState<T> { ... } implements
  10. Conceptos Clave Snapshot MutableState MutableState private class SnapshotMutableState<T>( value: T,

    val policy: SnapshotMutationPolicy<T> ) : StateObject, MutableState<T> { ... } implements
  11. @Composable fun Counter() { val count = remember { mutableStateOf(0)

    } Button( onClick = { count.value += 1 }, content = { Text("value: ${count.value}") } ) }
  12. Actualizando el value private var next: StateStateRecord<T> = StateStateRecord(value) override

    var value: T get() = next.readable(this).value set(value) = next.withCurrent { if (!policy.equivalent(it.value, value)) { next.writable(this) { this.value = value } } }
  13. Registrando el cambio private var next: StateStateRecord<T> = StateStateRecord(value) override

    var value: T get() = next.readable(this).value set(value) = next.withCurrent { if (!policy.equivalent(it.value, value)) { next.writable(this) { this.value = value } } }
  14. Conectar con el Snapshot inline fun writable(state: StateObject, block: T.()

    -> R): R { var snapshot: Snapshot = snapshotInitializer return sync { snapshot = Snapshot.current this.writableRecord(state, snapshot).block() }.also { notifyWrite(snapshot, state) } }
  15. Conectar con el Snapshot inline fun writable(state: StateObject, block: T.()

    -> R): R { var snapshot: Snapshot = snapshotInitializer return sync { snapshot = Snapshot.current this.writableRecord(state, snapshot).block() }.also { notifyWrite(snapshot, state) } }
  16. @Composable MutableState Snapshot actualiza value registra el cambio Write Observer

    Write Observer Write Observer Write Observer notify notify notify notify @Composable MutableState @Composable MutableState
  17. fun ComponentActivity.setContent( parent: CompositionReference = Recomposer.current(), content: @Composable () ->

    Unit ): Composition { GlobalSnapshotManager.ensureStarted() val composeView: AndroidOwner = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidOwner ?: AndroidComposeView(this).also { setContentView(it.view, DefaultLayoutParams) } return doSetContent(composeView, parent, content) }
  18. fun ComponentActivity.setContent( parent: CompositionReference = Recomposer.current(), content: @Composable () ->

    Unit ): Composition { GlobalSnapshotManager.ensureStarted() val composeView: AndroidOwner = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidOwner ?: AndroidComposeView(this).also { setContentView(it.view, DefaultLayoutParams) } return doSetContent(composeView, parent, content) }
  19. fun ComponentActivity.setContent( parent: CompositionReference = Recomposer.current(), content: @Composable () ->

    Unit ): Composition { GlobalSnapshotManager.ensureStarted() val composeView: AndroidOwner = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidOwner ?: AndroidComposeView(this).also { setContentView(it.view, DefaultLayoutParams) } return doSetContent(composeView, parent, content) }
  20. private fun doSetContent( owner: AndroidOwner, parent: CompositionReference, content: @Composable ()

    -> Unit ): Composition { val original = compositionFor( owner.root, UiApplier(owner.root), parent) val wrapped = WrappedComposition(owner, original).also { owner.view.setTag(R.id.wrapped_composition_tag, it) } wrapped.setContent(content) return wrapped }
  21. private fun doSetContent( owner: AndroidOwner, parent: CompositionReference, content: @Composable ()

    -> Unit ): Composition { val original = compositionFor( owner.root, UiApplier(owner.root), parent) val wrapped = WrappedComposition(owner, original).also { owner.view.setTag(R.id.wrapped_composition_tag, it) } wrapped.setContent(content) return wrapped }
  22. private fun doSetContent( owner: AndroidOwner, parent: CompositionReference, content: @Composable ()

    -> Unit ): Composition { val original = compositionFor( owner.root, UiApplier(owner.root), parent) val wrapped = WrappedComposition(owner, original).also { owner.view.setTag(R.id.wrapped_composition_tag, it) } wrapped.setContent(content) return wrapped }
  23. fun compositionFor( key: Any, applier: Applier<*>, parent: CompositionReference, onCreated: ()

    -> Unit = {} ): Composition = Compositions.findOrCreate(key) { CompositionImpl( parent, composerFactory = { slots, rcmpsr -> Composer(slots, applier, rcmpsr) }, onDispose = { Compositions.onDisposed(key) } ).also { onCreated() }
  24. setContent { ...} Composition crea (o accede a) un crea

    (o accede a) un se adhiere al padre Compositions se registra en AndroidOwner (AndroidComposeView)
  25. SlotTable Composer tiene un tiene un Composition (CompositionImpl) 1. Estructura

    de datos que almacena info de forma lineal utilizando gap buffers. 2. Almacena información que nos servirá al recomponer componentes. 3. Acceso / inserción de orden constante 4. Reestructuración de orden lineal (ya que tiene que mover el gap)
  26. SlotTable Composer tiene un tiene un Composition (CompositionImpl) 1. Realiza

    cambios (basados en positional memoization) sobre el SlotTable. 2. Coordina el "ciclo de vida” de un Composable (onEnter / onLeave) 3. Decide cómo actuar en base a cambios ocurridos en algún otro lugar.
  27. Conectar con el Snapshot class Recomposer(...) { ... suspend fun

    recomposeAndApplyChanges(...) { ... val observer = Snapshot.registerApplyObserver { changed, _ -> appliedChanges.offer(changed) } } ... }
  28. ¿Qué nos hace falta? Hay mucho más por detrás… 1.

    Hemos visto UN solo tipo de recomposición, la que está basada en estados. Hay más escenarios.
  29. ¿Qué nos hace falta? Hay mucho más por detrás… 2.

    Explorar Compose Compiler Plugin I. Es fundamental para que todo lo anterior funcione correctamente en tiempo de ejecución. II. Realiza optimizaciones a nivel de compilar para evitar recomposiciones innecesarias brunoaybar.com/slides/kapt-vs-plugin.pdf
  30. ¿Qué nos hace falta? Hay mucho más por detrás… 3.

    Compose UI core I. Sabe cómo interoperar con la plataforma (Android) II. Implica flujos de measuring y layout.
  31. ¿Qué nos hace falta? Hay mucho más por detrás… 4.

    Effect Handlers I. onCommit / onDispose II. SideEffect III. DisposableEffect IV. LaunchedEffect V. etc. jorgecastillo.dev/jetpack-compose-effect-handlers
  32. Jetpack Compose Recursos Oficiales: - Compose docs (goo.gle/compose-docs) - Compose

    Pathway (goo.gle/compose-pathway) - Compose Slack (goo.gle/compose-slack) - Official Compose Samples (goo.gle/compose-samples) - Android Code Search (https://cs.android.com/) Por la comunidad: - Compose Academy (https://compose.academy/) - Compose Cookbook (github.com/Gurupreet/ComposeCookBook) - Compose Insights (https://collectednotes.com/brunoaybar/compose- insights)
  33. @brunoaybarg Bruno125 Bruno Aybar Software Engineer @ Avantica Android Dev

    Peru co-organizer brunoaybar.com/slides/compose-state-recomposition.pdf