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

Hosting states in Compose

Hosting states in Compose

Dinorah Tovar

October 21, 2021
Tweet

More Decks by Dinorah Tovar

Other Decks in Technology

Transcript

  1. Hosting states in
    Compose
    Dinorah Tovar

    Google Developer Expert
    Platform Mobile Engineer

    @ konfío.mx
    @ddinorahtovar
    @ddinorahtovar

    View Slide

  2. But first,
    lifecycles

    View Slide

  3. Everything has some type of lifecycle
    @ddinorahtovar

    General Application
    Activity Fragment
    •Defines an object that has an Android Lifecycle
    Classes

    View Slide

  4. @Composable
    @ddinorahtovar
    •Composable functions are fast, idempotent, and the “remember”
    • Stateless functions
    •New data -> Recomposition (redraw if needed and only for the
    parts that are required)

    View Slide

  5. @Composable
    @ddinorahtovar
    •Composable functions works different, they emit data
    @Composable fun view(data: Int)
    data: Int Composition
    emit

    View Slide

  6. States and
    recomposition

    View Slide

  7. States
    @ddinorahtovar
    •Are, pretty much, any value that can change over time.
    Name How
    States
    A composable has to
    explicitly be told the
    new state in order for
    it to update
    accordingly.
    Why
    When the state
    is updated, we
    go over a
    process of
    Recomposition

    View Slide

  8. States
    @ddinorahtovar
    •Holds a value (Can be read and updated)
    interface State
    •The current RecomposeScope will be subscribed to changes of
    that value

    View Slide

  9. States
    @ddinorahtovar
    interface MutableState : State {


    override var value: T


    }
    •mutableStateOf creates an observable MutableState,
    which is an observable type integrated with the compose
    runtime.

    View Slide

  10. Init composition vs recomposition
    @ddinorahtovar
    First Interaction Composition
    Insert “A”
    Insert “B”
    Second Interaction
    Re-composition

    View Slide

  11. Compose
    @ddinorahtovar
    •The composition is the state maintained by the memory
    Composition


    Slot Table
    Composer
    Compose Runtime

    View Slide

  12. Compose Runtime
    @ddinorahtovar
    Composition


    Compose Runtime
    Slot Table
    Composer
    •Fundamental building blocks of Compose's
    programming model and state management,
    and core runtime for the Compose Compiler
    Plugin to target.
    •Handle everything for the states

    View Slide

  13. Slot table
    @ddinorahtovar
    Composition


    Slot Table
    Composer
    Compose Runtime
    •Current state of the Composition
    •Updates with every composition
    •Gap Buffer - data structure
    •Positional Memorization

    View Slide

  14. Slot table
    @ddinorahtovar
    Unused Used B
    Used A
    Internal array
    Gap
    Collection
    Emit
    Composition


    Slot Table
    Composer
    Compose Runtime
    Collect B
    Collect A

    View Slide

  15. Composer
    @ddinorahtovar
    Composition


    Composer
    Compose Runtime
    Slot Table
    fun MultiMeasureLayout(


    modifier: Modifier = Modifier,


    content: @Composable () -> Unit,


    measurePolicy: MeasurePolicy


    ) {


    val materialized = currentComposer.materialize(modifier)


    val density = LocalDensity.current


    val layoutDirection = LocalLayoutDirection.current


    ReusableComposeNode>(


    factory = LayoutNode.Constructor,


    update = {


    set(materialized, ComposeUiNode.SetModifier)


    set(measurePolicy, ComposeUiNode.SetMeasurePolicy)


    set(density, ComposeUiNode.SetDensity)


    set(layoutDirection, ComposeUiNode.SetLayoutDirection)


    @Suppress("DEPRECATION")


    init { this.canMultiMeasure = true }


    },


    content = content


    )


    }

    View Slide

  16. Composer
    @ddinorahtovar
    Composition


    Composer
    Compose Runtime
    Slot Table
    fun MultiMeasureLayout(


    modifier: Modifier = Modifier,


    content: @Composable () -> Unit,


    measurePolicy: MeasurePolicy


    ) {


    val materialized = currentComposer.materialize(modifier)


    val density = LocalDensity.current


    val layoutDirection = LocalLayoutDirection.current


    ReusableComposeNode>(


    factory = LayoutNode.Constructor,


    update = {


    set(materialized, ComposeUiNode.SetModifier)


    set(measurePolicy, ComposeUiNode.SetMeasurePolicy)


    set(density, ComposeUiNode.SetDensity)


    set(layoutDirection, ComposeUiNode.SetLayoutDirection)


    @Suppress("DEPRECATION")


    init { this.canMultiMeasure = true }


    },


    content = content


    )


    }

    View Slide

  17. Composer
    @ddinorahtovar
    Composition


    Composer
    Compose Runtime
    Slot Table
    @Composable


    inline fun > ReusableComposeNode(


    noinline factory: () -> T,


    update: @DisallowComposableCalls Updater.() -> Unit,


    content: @Composable () -> Unit


    ) {


    if (currentComposer.applier !is E) invalidApplier()


    currentComposer.startReusableNode()


    if (currentComposer.inserting) {


    currentComposer.createNode(factory)


    } else {


    currentComposer.useNode()


    }


    currentComposer.disableReusing()


    Updater(currentComposer).update()


    currentComposer.enableReusing()


    content()


    View Slide

  18. Recompose and
    Remember

    View Slide

  19. Scopes for recomposition
    @ddinorahtovar
    •The composition is the state maintained by the memory
    🧠

    View Slide

  20. Remember 🧠
    @ddinorahtovar
    •@Composable fun can store a single object in memory by using
    the remember composable
    •Remember the value produced by calculation
    •calculation will only be evaluated during the composition
    •Recomposition will always return the value produced by
    composition

    View Slide

  21. Remember
    @ddinorahtovar
    @Composable


    fun View() {


    val value = remember { mutableStateOf("") }


    var someValue by remember { mutableStateOf("") }


    val (someOtherValue, setValue) = remember { mutableStateOf("") }


    }

    View Slide

  22. Remember
    @ddinorahtovar
    @Composable


    inline fun remember(calculation: @DisallowComposableCalls () -> T): T =


    currentComposer.cache(false, calculation)
    @Composable


    fun View() {


    var variable by remember { mutableStateOf("") }


    }

    View Slide

  23. Remember
    @ddinorahtovar
    •Remember is the one that conserve the state, not the
    @Composable fun
    @Composable fun view(data: Int)
    data: Int Composition
    emit
    remember by { }

    View Slide

  24. Remember
    @ddinorahtovar
    •So this save us from activity recreation?
    •Short answer: No

    View Slide

  25. RememberSaveable
    @ddinorahtovar
    @Composabke


    fun View() {


    var variable = rememberSaveable { mutableStateOf("") }


    }

    View Slide

  26. RememberSaveable
    @ddinorahtovar
    @Composable


    fun rememberSaveable(


    vararg inputs: Any?,


    saver: Saver = autoSaver(),


    key: String? = null,


    init: () -> T


    ): T {


    val finalKey = if (!key.isNullOrEmpty()) {


    key


    } else {


    currentCompositeKeyHash.toString()


    }


    @Suppress("UNCHECKED_CAST")


    (saver as Saver)


    val registry = LocalSaveableStateRegistry.current


    val value = remember(*inputs) {


    val restored = registry?.consumeRestored(finalKey)?.let {


    saver.restore(it)


    }


    restored ?: init()


    }


    View Slide

  27. RememberSaveable
    @ddinorahtovar
    val registry = LocalSaveableStateRegistry.current


    val value = remember(*inputs) {


    val restored = registry?.consumeRestored(finalKey)?.let {


    saver.restore(it)


    }


    restored ?: init()


    }


    val saverHolder = remember { mutableStateOf(saver) }


    saverHolder.value = saver


    if (registry != null) {


    DisposableEffect(registry, finalKey, value) {


    val valueProvider = {


    with(saverHolder.value) { SaverScope { registry.canBeSaved(it) }.save(value) }


    }


    registry.requireCanBeSaved(valueProvider())


    val entry = registry.registerProvider(finalKey, valueProvider)


    onDispose {


    entry.unregister()


    }


    }


    }


    return value


    }

    View Slide

  28. State hosting
    @ddinorahtovar
    •Is a pattern that to move all the state variables to the top-level
    composable function and pass them as arguments to each
    composable function based on the requirement

    Decoupling
    @Composable

    fun View()
    @Composable

    fun View()
    @Composable

    fun ViewContent()

    View Slide

  29. State Hosting
    @ddinorahtovar
    @Composable


    fun View(){


    var variable by remember { mutableStateOf("") }


    val onValueChange = { text : String->


    variable = text


    }


    Component(name= variable, onValueChange= onValueChange )


    }


    @Composable


    fun ViewContent(name : String, onValueChange : (String) -> Unit ){


    // Your view


    }

    View Slide

  30. State hosting
    @ddinorahtovar

    Decoupling
    @Composable

    fun View()
    @Composable

    fun View()
    @Composable

    fun ViewContent()
    Unidirectional

    Data Flows

    View Slide

  31. State Holders
    @ddinorahtovar
    •Manage complex state UI elements.
    •They own UI elements' state and UI logic.
    •State holders are compoundable
    •ViewModels, Objects or pretty much anything 😅

    View Slide

  32. State Holders
    @ddinorahtovar
    @Composable State Holder
    ViewModel/
    Presenters
    Access to

    Logic
    UI component
    state
    UI component
    Composition

    View Slide

  33. State Holders
    @ddinorahtovar
    @Composable are in charge of
    emi
    tt
    ing UI elements

    State holder only contains the UI
    logic and state

    View Slide

  34. StateFlow and
    SharedFlow collection

    View Slide

  35. SharedFlow and StateFlow
    @ddinorahtovar

    View Slide

  36. SharedFlow and StateFlow collection
    •What do we really need?
    @ddinorahtovar
    •Launch and Run in specific stage
    •Cancel when is not longer in a stage
    •Automatic relaunch and cancellation

    View Slide

  37. SharedFlow and StateFlow collection
    @ddinorahtovar
    Safe collection

    Using a lib


    repeatOnLifecycle()
    flowWithLifecycle()
    •It’s on alpha, but it works
    implementation: “androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha02"

    View Slide

  38. repeatOnLifecycle()
    @ddinorahtovar
    Safe collection

    Using a lib


    repeatOnLifecycle()
    flowWithLifecycle()
    private fun setObservables() = lifecycleScope.launch {

    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {

    viewModel.uiHomeState.collect {

    //
    Safe collect

    }

    }

    }

    View Slide

  39. Under the hood of: repeatOnLifecycle()
    @ddinorahtovar
    public suspend fun Lifecycle.repeatOnLifecycle(

    state: Lifecycle.State,

    block: suspend CoroutineScope.()
    - >
    Unit

    ) {

    require(state
    ! ==
    Lifecycle.State.INITIALIZED) {

    "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."

    }

    if (currentState
    = ==
    Lifecycle.State.DESTROYED) {

    return

    }

    coroutineScope {

    withContext(Dispatchers.Main.immediate) {

    if (currentState
    ===
    Lifecycle.State.DESTROYED) return@withContext

    var launchedJob: Job? = null

    var observer: LifecycleEventObserver? = null

    }

    }

    }

    View Slide

  40. Under the hood of: repeatOnLifecycle()
    @ddinorahtovar
    public suspend fun Lifecycle.repeatOnLifecycle(

    state: Lifecycle.State,

    block: suspend CoroutineScope.()
    - >
    Unit

    ) {

    require(state
    ! ==
    Lifecycle.State.INITIALIZED) {

    "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."

    }

    if (currentState
    = ==
    Lifecycle.State.DESTROYED) {

    return

    }

    coroutineScope {

    withContext(Dispatchers.Main.immediate) {

    if (currentState
    ===
    Lifecycle.State.DESTROYED) return@withContext

    var launchedJob: Job? = null

    var observer: LifecycleEventObserver? = null

    }

    }

    }

    View Slide

  41. Under the hood of: repeatOnLifecycle()
    @ddinorahtovar
    try {

    suspendCancellableCoroutine { cont
    ->


    val startWorkEvent = Lifecycle.Event.upTo(state)

    val cancelWorkEvent = Lifecycle.Event.downFrom(state)

    observer = LifecycleEventObserver { _, event
    ->


    if (event
    ==
    startWorkEvent) {

    launchedJob = [email protected](block = block)

    return@LifecycleEventObserver

    }

    if (event
    ==
    cancelWorkEvent) {

    launchedJob
    ?.
    cancel()

    launchedJob = null

    }

    if (event
    ==
    Lifecycle.Event.ON_DESTROY) {

    cont.resume(Unit)

    }

    }

    [email protected](observer as LifecycleEventObserver)

    }

    } finally {

    ?.

    View Slide

  42. ==
    launchedJob = [email protected](block = block)

    return@LifecycleEventObserver

    }

    if (event
    ==
    cancelWorkEvent) {

    launchedJob
    ? .
    cancel()

    launchedJob = null

    }

    if (event
    ==
    Lifecycle.Event.ON_DESTROY) {

    cont.resume(Unit)

    }

    }

    [email protected](observer as LifecycleEventObserver)

    }

    } finally {

    launchedJob
    ?.
    cancel()

    observer
    ?.
    let {

    [email protected](it)

    }

    }
    Under the hood of: repeatOnLifecycle()
    @ddinorahtovar

    View Slide

  43. repeatOnLifecycle() on fragments
    @ddinorahtovar
    Safe collection

    Using a lib


    repeatOnLifecycle()
    flowWithLifecycle()
    private fun setObservables() = viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {

    viewModel.uiHomeState.collect {

    //
    Safe collect

    }

    }

    }

    View Slide

  44. flowWithLifecycle()
    @ddinorahtovar
    lifecycleScope.launch {

    viewModel.state

    .flowWithLifecycle(this, Lifecycle.State.STARTED).collect {

    //
    Safe collect

    }

    }
    Safe collection

    Using a lib


    repeatOnLifecycle()
    flowWithLifecycle()

    View Slide

  45. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = callbackFlow {

    lifecycle.repeatOnLifecycle(minActiveState) {

    [email protected] {

    send(it)

    }

    }

    close()

    }

    View Slide

  46. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = callbackFlow {

    lifecycle.repeatOnLifecycle(minActiveState) {

    [email protected] {

    send(it)

    }

    }

    close()

    }
    Cold flow
    •With elements that are sent
    to a SendChannel with a
    block
    ->
    ProducerScope.

    •It allows elements to be
    produced by code that is
    running in a different
    context or concurrently

    View Slide

  47. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = callbackFlow {

    lifecycle.repeatOnLifecycle(minActiveState) {

    [email protected] {

    send(it)

    }

    }

    close()

    }

    View Slide

  48. Collecting flows in @Composables
    @ddinorahtovar
    @Composable


    fun View(someFlow: Flow) {


    val lifecycleOwner = LocalLifecycleOwner.current


    val flowWithLifecycleAware = remember(someFlow, lifecycleOwner) {


    someFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)


    }


    val yourValueThatChange by flowWithLifecycleAware.collectAsState()


    // Your Composable component


    }

    View Slide

  49. Give it a try!

    View Slide

  50. Hosting states in
    Compose
    Dinorah Tovar

    Google Developer Expert
    Platform Mobile Engineer

    @ konfío.mx
    @ddinorahtovar
    @ddinorahtovar

    View Slide