understanding compose

types

ComponentActivity::setContent

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
)
  1. 1.
    obtains a ComposeView to upsert either from this.window.decorView or by constructing one
  2. 2.
    updates the ComposeView
    1. 1.
      on the ComposeView, calls .setParentCompositionContext(parent); sets this.parentContext = parent
      1. 1.
        the setter might call this.ensureCompositionCreated()
    2. 2.
      on the ComposeView, calls .setContent(content)
      1. 1.
        sets this.content.value = content
      2. 2.
        if this.isAttachedToWindow, calls this.createComposition() (→ this.ensureCompositionCreated())
  3. 3.
    inserts the ComposeView if it’s new
    1. 1.
      on the ComponentActivity, calls .setOwners(), which passes this: ComponentActivity
      • if this.window.decorView.findViewTree{Lifecycle, ViewModelStore, SavedStateRegistry}Owner() == null
      • to this.window.decorView.setViewTree{Lifecycle, ViewModelStore, SavedStateRegistry}Owner respectively
    2. 2.
      on the ComponentActivity, calls .setContentView(/* ComposeView */, DefaultActivityContentLayoutParams)
      private val DefaultActivityContentLayoutParams = ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT,
      )
      • populates the activity window decor view with the new ComposeView
      • the layout parameters request the window make the view as tall and wide as its content

AbstractComposeView::ensureCompositionCreated

  1. 1.
    calls this.resolveParentCompositionContext()
    1. 1.
      returns this.parentContext if !null
    2. 2.
      returns this.findViewTreeCompositionContext()?.cacheIfAlive() if !null
      1. 1.
        goes up the View hierarchy tree until the first View::compositionContext: CompositionContext? that’s !null found
      2. 2.
        if !null, used if .isAlive; cached in this.cachedViewTreeCompositionContext = WeakReference(it)
    3. 3.
      returns this.cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive } if !null
    4. 4.
      returns this.windowRecomposer.cacheIfAlive() otherwise; View::windowRecomposer: Recomposer is a computed property
      1. 1.
        expects (invariant) this.isAttachedToWindow
      2. 2.
        goes up the View hierarchy tree for the first one parented by a android.R.id.content view
      3. 3.
        if the View::compositionContext: CompositionContext? for the found view
        • is Recomposer → returns the Recomposer
        • nullWindowRecomposerPolicy.createAndInstallWindowRecomposer passed the found view
        • else → throws if reached
  2. 2.
    calls this.setContent
    internal fun AbstractComposeView.setContent(
        parent: CompositionContext,
        content: @Composable () -> Unit,
    ): Composition
    • extension; unrelated to ComposeView::setContent by inheritance
    • parent passed the return value of this.resolveParentCompositionContext()
    • content calls this.Content() when called
    1. 1.
      calls GlobalSnapshotManager.ensureStarted()
    2. 2.
      obtains a AndroidComposeView either from this.mChildren or by constructing one
      1. 1.
        this.getChildAt(0) as? AndroidComposeView used if this.childCount > 0
      2. 2.
        otherwise uses AndroidComposeView(this.context, parent.effectCoroutineContext)
        • View::context: Context; an environment role valid for a lifetime & shared by every reference site
        • the AndroidComposeView (added) made the only child of the this: AbstractComposeView parent (all other children removed)
    3. 3.
      return doSetContent(/* ... */)
      private fun doSetContent(
          owner: AndroidComposeView,
          parent: CompositionContext,
          content: @Composable () -> Unit,
      ): Composition
      • owner passed the obtained AndroidComposeView child
      • parent relayed the parent: CompositionContext from AbstractComposeView::setContent
      • content relayed the content: @Composable () -> Unit from AbstractComposeView::setContent
      1. 1.
        obtains a WrappedComposition: Composition either from the owner or by constructing one
        1. 1.
          owner.view.getTag(R.id.wrapped_composition_tag) as? WrappedComposition used if !null
        2. 2.
          otherwise WrappedComposition(owner, Composition(UiApplier(owner.root), parent)) added to the owner and used
          public fun Composition(applier: Applier<*>, parent: CompositionContext)
              : Composition
              = CompositionImpl(parent, applier)
      2. 2.
        calls .setContent(content) on the obtained WrappedComposition and then returns it

(WrappedComposition as Composition)::setContent

override fun setContent(content: @Composable () -> Unit)
  1. 1.
    obtains the ViewTreeOwners of this.owner: AndroidComposeView if it’s attached to a view, or on attach later
    • on the initial (<activity> as ComponentActivity)::onCreateComponentActivity::setContentComposeView::setContent call path
      • called before it’s attached to a window; AbstractComposeView::ensureCompositionCreated not reached
      • attached to a window after (<activity> as ComponentActivity)::onCreate has returned
    • on the initial attach, AbstractComposeView::onAttachedToWindow called; its children attached after the callback has returned (ie not yet)
      • AbstractComposeView::onAttachedToWindowAbstractComposeView::ensureCompositionCreatedAbstractComposeView::setContentdoSetContentWrappedComposition::setContent called
      • WrappedComposition::owner: AndroidComposeView not attached yet, but owner.parent as AbstractComposeView is
    • after AbstractComposeView::onAttachedToWindow has returned, this.owner: AndroidComposeView attached
      • AndroidComposeView::onAttachedToWindow called; instantiates ViewTreeOwners → obtained for the remaining steps
      • the ComponentActivity registered for (this: AndroidComposeView).findViewTree{Lifecycle, SavedStateRegistry, ViewModelStore}Owner()
  2. 2.
    returns if this.disposed, otherwise sets this.lastContent = content and continues
  3. 3.
    if this.addedToLifecycle == null; true if the initialiser hasn’t been overridden
    1. 1.
      assigns the .lifecycleOwner.lifecycle: Lifecycle of the obtained ViewTreeOwners to this.addedToLifecycle
      • that of the ComponentActivity on this code path; already created, started, and resumed
    2. 2.
      calls .addObserver(this) on it; observed by this: LifecycleEventObserver
      • addObserver brings this to the current state of this.addedToLifecycle (created → started → resumed)
      • Lifecycle.Event.ON_DESTROYthis.dispose()
      • Lifecycle.Event.ON_CREATE (called on add here) → calls this.setContent(this.lastContent)
  4. 4.
    otherwise (if this.addedToLifecycle != null), if the observed lifetime .currentState.isAtLeast(Lifecycle.State.CREATED), provides the following steps for updating (calculating and applying changes to) its original: Composition
    • calls CompositionImpl::setContent on this call path
    1. 1.
      substep 1

CompositionImpl::setContent

override fun setContent(content: @Composable () -> Unit)
  1. 1.
    caches this.state == DEACTIVATED and assigns RUNNING to the field
  2. 2.
    checks the invariants; throws unless
    • this.state == RUNNING
      • INCONSISTENTthis: CompositionImpl must be disposed because the previous composition was cancelled
      • DISPOSED → invalid for use because this: CompositionImpl has been disposed of
      • DEACTIVATED → should be activated again before setting the content
    • this.pendingPausedComposition == null; a pausable composition is already in progress
  3. 3.
    if (this: CompositionImpl).state was DEACTIVATED per the cache, calls this.composer.startReuseFromRoot()
    1. 1.
      (this: ComposerImpl) sets this.reusingGroup = /* static */ rootKey; this.reusing = true
  4. 4.
    unconditionally sets (this: CompositionImpl).composable = content
  5. 5.
    unconditionally calls (this: CompositionImpl).parent.composeInitial(this, composable)
    • CompositionContext::composeInitial delegates up the hierarchy to the Recomposer: CompositionContext impl
  6. 6.
    if (this: CompositionImpl).state was DEACTIVATED per the cache, calls this.composer.endReuseFromRoot()
    1. 1.
      (this: ComposerImpl) checks the invariant; throws unless !this.isComposing && this.reusingGroup == rootKey
    2. 2.
      (this: ComposerImpl) sets this.reusingGroup = -1; this.reusing = false

Recomposer::composeInitial

internal override fun composeInitial(
    composition: ControlledComposition,
    content: @Composable () -> Unit,
)

Recomposer::composing

private inline fun <T> composing(
    composition: ControlledComposition,
    modifiedValues: MutableScatterSet?,
    block: () -> T,
): T
  1. 1.
    takes a mutable snapshot with
    • a read observer: { value -> composition.recordReadOf(value) }
    • a write observer:
      { value ->
          composition.recordWriteOf(value)
          modifiedValues?.add(value)
      }
  2. 2.
    enters the snapshot and runs the block
    • fully applies ControlledComposition::composeContent to the arguments of Recomposer::composeInitial
  3. 3.
    relays the return value of the block after applying the snapshot; throws & disposes on failure

(CompositionImpl as ControlledComposition)::recordReadOf

  1. 1.
    i don’t fucking know

(CompositionImpl as ControlledComposition)::recordWriteOf

  1. 1.
    i don’t fucking know