internal class ComposerImpl: Composer
Composer@Composable at compile timepublic abstract class CompositionContext
public val effectCoroutineContext: CoroutineContext; effects for the composition (eg LaunchedEffect)internal abstract fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit,
)internal open fun registerComposer(composer: Composer) {}; nop by defaultpublic class Recomposer; the context of a root composition
private val _knownCompositions = mutableListOf<ControlledComposition>()
private var _knownCompositionsCache: List<ControlledComposition>? = null; assigned a clone on mutationinternal inner class CompositionContextImpl; a child context organised into a tree
ComposerImpl) that constructed it
CompositionContextImpl::composeInitial delegates to ComposerImpl::parentContext: CompositionContextval composers = mutableSetOf<ComposerImpl>()override fun registerComposer(composer: Composer); adds the argument to this.composersinternal class RecomposeScopeImpl
internal var owner: RecomposeScopeOwner?public interface RecomposeScope
public fun invalidate()
ComposerRecomposeScopeImpl::invalidate calls this.owner?.invalidate(this, null)public interface ScopeUpdateScopepublic interface IdentifiableRecomposeScopepublic interface Composition
public fun dispose(); should free resources and invalidate itself for further usepublic fun setContent(content: @Composable () -> Unit); should update (calculate and apply changes to) itself as described by the content.public val hasInvalidations: Boolean; should be true if RecomposeScope::invalidate has been called in the compositionpublic val isDisposed: Boolean; should be true if the dispose method has been calledprivate class WrappedComposition: Composition, LifecycleEventObserver, CompositionServices
val owner: AndroidComposeView; owned by an AndroidComposeView: ViewGroupval original: Composition; the composition this: WrappedComposition wraps → intercepts & handles accessprivate var lastContent: @Composable () -> Unit = {}private var addedToLifecycle: Lifecycle? = null
Lifecycle in setContent if available.addObserver(this); observed by this: LifecycleEventObserver
Lifecycle.Event.ON_DESTROY → this.dispose()Lifecycle.Event.ON_CREATE → if !this.disposed, calls this.setContent(this.lastContent)addObserver brings this to the current state of this.addedToLifecycleoverride fun setContent(content: @Composable () -> Unit); assigns content to this.lastContent + arranges a call to original::setContentinternal class CompositionImpl
private var state = RUNNING
private const val RUNNING = 0 in CompositionImpl::clearDeactivatedprivate const val DEACTIVATED = 1 in CompositionImpl::deactivateprivate const val INCONSISTENT = 2 in CompositionImpl::pausedCompositionFinished conditionallyprivate const val DISPOSED = 3 in CompositionImpl::disposeval parent: CompositionContext; initialised on instantiation with an argumentinternal val composer: ComposerImpl = ComposerImpl(
applier = applier,
parentContext = parent,
slotTable = slotTable,
abandonSet = abandonSet,
changes = changes,
lateChanges = lateChanges,
composition = this,
observerHolder = observerHolder,
)
.also { parent.registerComposer(it) }public sealed interface ControlledComposition: Composition
public val isComposing: Boolean; implemented by this: CompositionImpl to return this.composer.isComposingpublic sealed interface ReusableComposition: Composition public sealed interface PausableComposition: ReusableCompositionpublic interface ObservableComposition; disjoint interfaceinternal interface RecomposeScopeOwnerpublic abstract class ViewGroup
android.view.View; a tree (node) of interactive UI elementsView::dispatchAttachedToWindow called on this: ViewGroup then on each in this.{mChildren, mTransientViews}
View::isAttachedToWindow is true for the receiver; not yet for its childrenView::onAttachedToWindow called on the receiver; not yet on its childrensuper impl of View::dispatchAttachedToWindow on the receiver has returnedinternal class AndroidComposeView
DefaultLifecycleObserver: LifecycleObserverOwner; the connection from compose to the android view system
val root: LayoutNode; the root of the composition tree that (this: AndroidComposeView) as Owner owns/hostsViewTreeOwners class unrelated by inheritance
val lifecycleOwner: LifecycleOwnerval savedStateRegistryOwner: SavedStateRegistryOwnerval viewModelStoreOwner: ViewModelStoreOwner?private var _viewTreeOwners: ViewTreeOwners? by mutableStateOf(null)val viewTreeOwners: ViewTreeOwners? by derivedStateOf { _viewTreeOwners }override fun onAttachedToWindow() instantiates ViewTreeOwners and assigns it to this._viewTreeOwners with
this.findViewTreeLifecycleOwner(): LifecycleOwner?this.findViewTreeSavedStateRegistryOwner(): SavedStateRegistryOwner?this.findViewTreeViewModelStoreOwner(): ViewModelStoreOwner?private var onViewTreeOwnersAvailable: ((ViewTreeOwners) -> Unit)? = null
this.viewTreeOwners) if !null then cleared by onAttachedToWindowabstract class AbstractComposeView
private var composition: Composition? = null
this: AbstractComposeView is to have an AndroidComposeView childthis references the composition backed by the Owner::root tree owned by the child viewprivate fun ensureCompositionCreated(); assigns a Composition to this.composition if nulloverride fun onAttachedToWindow(); calls this.ensureCompositionCreated()private var cachedViewTreeCompositionContext: WeakReference<CompositionContext>? = nullprivate var parentContext: CompositionContext? = null; when assigned a !null value, the setter
this.cachedViewTreeCompositionContext = nullthis.composition != null, calls .dispose() on & assigns null to it
this.isAttachedToWindow, calls this.ensureCompositionCreated()fun createComposition()
this.ensureCompositionCreated()ComposeView::setContent if this.isAttachedToWindowabstract fun Content()
@Composable) of this viewthis.createComposition() is calledclass ComposeView
private val content: MutableState<(@Composable () -> Unit)?> = mutableStateOf(null)override fun Content(); calls this.content.value?.invoke()public abstract class AbstractApplier<T>
Applier<T>; manipulates a tree of T nodes
val current: T; the target of operationsoverride var current: T; protected set; the subtype supports traversing the treeval root: T; references the same object throughout the lifetime of the applierprivate val stack = Stack() ; a FIFO stack that keeps track of the nodes it’s visitedinternal class UiApplier: AbstractApplier<LayoutNode>
LayoutNode tree (eg (AndroidComposeView as Owner)::root: LayoutNode)ComponentActivity::setContentpublic fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
)
content into this: ComponentActivity; populates the activity window view treeComposeView to upsert either from this.window.decorView or by constructing oneComposeView
ComposeView, calls .setParentCompositionContext(parent); sets this.parentContext = parent
this.ensureCompositionCreated()ComposeView, calls .setContent(content)
this.content.value = contentthis.isAttachedToWindow, calls this.createComposition() (→ this.ensureCompositionCreated())ComposeView if it’s new
ComponentActivity, calls .setOwners(), which passes this: ComponentActivity
this.window.decorView.findViewTree{Lifecycle, ViewModelStore, SavedStateRegistry}Owner() == nullthis.window.decorView.setViewTree{Lifecycle, ViewModelStore, SavedStateRegistry}Owner respectivelyComponentActivity, calls .setContentView(/* ComposeView */, DefaultActivityContentLayoutParams)
private val DefaultActivityContentLayoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
ComposeViewAbstractComposeView::ensureCompositionCreatedthis.composition == null, resolves its parent and constructs a Compositionthis.isAttachedToWindow; this attached to a windowthis.resolveParentCompositionContext()
this.parentContext if !nullthis.findViewTreeCompositionContext()?.cacheIfAlive() if !null
View hierarchy tree until the first View::compositionContext: CompositionContext? that’s !null found!null, used if .isAlive; cached in this.cachedViewTreeCompositionContext = WeakReference(it)this.cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive } if !nullthis.windowRecomposer.cacheIfAlive() otherwise; View::windowRecomposer: Recomposer is a computed property
this.isAttachedToWindowView hierarchy tree for the first one parented by an R.id.content viewView::compositionContext: CompositionContext? for the found view
is Recomposer → returns the Recomposernull → WindowRecomposerPolicy.createAndInstallWindowRecomposer passed the found viewelse → throws if reachedthis.setContent
internal fun AbstractComposeView.setContent(
parent: CompositionContext,
content: @Composable () -> Unit,
): Composition
ComposeView::setContent by inheritanceparent passed the return value of this.resolveParentCompositionContext()content calls this.Content() when calledGlobalSnapshotManager.ensureStarted()AndroidComposeView either from this.mChildren or by constructing one
this.getChildAt(0) as? AndroidComposeView used if this.childCount > 0AndroidComposeView(this.context, parent.effectCoroutineContext)
View::context: Context; an environment role valid for a lifetime & shared by every reference siteAndroidComposeView (added) made the only child of the this: AbstractComposeView parent (all other children removed)return doSetContent(/* ... */)
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit,
): Composition
owner passed the obtained AndroidComposeView childparent relayed the parent: CompositionContext from AbstractComposeView::setContentcontent relayed the content: @Composable () -> Unit from AbstractComposeView::setContentWrappedComposition: Composition either from the owner or by constructing one
owner.view.getTag(R.id.wrapped_composition_tag) as? WrappedComposition used if !nullWrappedComposition(owner, Composition(UiApplier(owner.root), parent)) added to the owner and used
public fun Composition(applier: Applier<*>, parent: CompositionContext)
: Composition
= CompositionImpl(parent, applier)
parent is the Recomposer: CompositionContext returned and relayed from AbstractComposeView::resolveParentCompositionContextCompositionImpl constructed with a ComposerImpl registered with the parent; nop.setContent(content) on the obtained WrappedComposition and then returns it(WrappedComposition as Composition)::setContentoverride fun setContent(content: @Composable () -> Unit)
content relayed the content: @Composable () -> Unit from doSetContent, relayed from AbstractComposeView::setContentViewTreeOwners of this.owner: AndroidComposeView if it’s attached to a view, or on attach later
(<activity> as ComponentActivity)::onCreate → ComponentActivity::setContent → ComposeView::setContent call path
AbstractComposeView::ensureCompositionCreated not reached(<activity> as ComponentActivity)::onCreate has returnedAbstractComposeView::onAttachedToWindow called; its children attached after the callback has returned (ie not yet)
AbstractComposeView::onAttachedToWindow → AbstractComposeView::ensureCompositionCreated → AbstractComposeView::setContent → doSetContent → WrappedComposition::setContent calledWrappedComposition::owner: AndroidComposeView not attached yet, but owner.parent as AbstractComposeView isAbstractComposeView::onAttachedToWindow has returned, this.owner: AndroidComposeView attached
AndroidComposeView::onAttachedToWindow called; instantiates ViewTreeOwners → obtained for the remaining stepsComponentActivity registered for (this: AndroidComposeView).findViewTree{Lifecycle, SavedStateRegistry, ViewModelStore}Owner()this.disposed, otherwise sets this.lastContent = content and continuesthis.addedToLifecycle == null; ie if it’s the first time the flow has reached the conditional
.lifecycleOwner.lifecycle: Lifecycle of the obtained ViewTreeOwners to this.addedToLifecycle
ComponentActivity on this code path; already created, started, and resumed.addObserver(this) on it; observed by this: LifecycleEventObserver
addObserver brings this to the current state of this.addedToLifecycle (created → started → resumed)Lifecycle.Event.ON_DESTROY → this.dispose()Lifecycle.Event.ON_CREATE (called on add here) → calls this.setContent(this.lastContent)this.addedToLifecycle != null), provides the following steps for updating (calculating and applying changes to) its original: Composition if the observed lifetime .currentState.isAtLeast(Lifecycle.State.CREATED)
CompositionImpl::setContent on this call pathMutableSet<CompositionData>? if present from either the AndroidComposeView or its parentcontent relayed from (AbstractComposeView::setContent →) doSetContent to be run with the provided composition locals
CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
ProvideAndroidCompositionLocals(this.owner, content)
}
LocalInspectionTables mapped to the inspection data set retrieved and populated above
.current: MutableSet<CompositionData>? visible in contentinternal fun ProvideAndroidCompositionLocals(
owner: AndroidComposeView,
content: @Composable () -> Unit,
)
owner passed the WrappedComposition::owner: AndroidComposeViewcontent relayed the content: @Composable () -> Unit from (AbstractComposeView::setContent →) doSetContentcontent
LocalContext mapped to owner.context: Context!CompositionImpl::setContentoverride fun setContent(content: @Composable () -> Unit)
this: CompositionImpl by the caller
@Composable registered via AbstractComposeView::setContentthis.clearDeactivated(): Boolean; sets this.state = RUNNING if this.state == DEACTIVATED & returns the predicatethis.state == RUNNING; either new (RUNNING) or reactivated (RUNNING → DEACTIVATED)
INCONSISTENT → this: CompositionImpl must be disposed of because the previous composition was cancelledDISPOSED → invalid for use because this: CompositionImpl has been disposed ofDEACTIVATED → should be activated again before setting the contentthis.pendingPausedComposition == null; a pausable composition is already in progress(this.composer: ComposerImpl).startReuseFromRoot()
fun startReuseFromRoot() {
reusingGroup = rootKey
reusing = true
}(this: CompositionImpl).composeInitial(content); relays the provided steps to expose itself to tooling, launch effects, and run the registered @Composable
this.composable = content; saves the provided steps to update (calculate and apply changes to) itselfthis.parent.composeInitial(this, this.composable)
CompositionContext::composeInitial delegates up the hierarchy to the Recomposer: CompositionContext implthis.composer.endReuseFromRoot()
fun endReuseFromRoot() {
requirePrecondition(!isComposing && reusingGroup == rootKey) {
"Cannot disable reuse from root if it was caused by other groups"
}
reusingGroup = -1
reusing = false
}Recomposer::composeInitialinternal override fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit,
)
composition passed the receiver of CompositionImpl::setContent from it
WrappedComposition::original: Composition on this call pathcontent relayed the content: @Composable () -> Unit from CompositionImpl::setContent
CompositionImpl provided in WrappedComposition::setContent@Composable registered via AbstractComposeView::setContentComposerImpl::{recomposeToGroupEnd, recomposeMovableContent, prepareCompose, doCompose}this.registerCompositionLocked(composition) if composition !in this.knownCompositionsLocked()
Recomposer::registrationObservers: MutableObjectList<CompositionRegistrationObserver>? if the composition is ObservableCompositionthis.composing(composition, null) { composition.composeContent(content) } and returns on throw
private inline fun <T> composing(
composition: ControlledComposition,
modifiedValues: MutableScatterSet<Any>?,
block: () -> T,
): T
composition relayed the composition: ControlledComposition from Recomposer::composeInitialblock passed a partial application of composition::composeContent to content
content executes the provided steps to update WrappedComposition::original: Composition in a snapshot{ value -> composition.recordReadOf(value) }{ value ->
composition.recordWriteOf(value)
modifiedValues?.add(value)
}block; fully applies composition::composeContent to contentblock after applying the snapshot; throws & disposes on failurethis.addKnownCompositionLocked(composition) if composition !in this.knownCompositionsLocked()
Recomposer::_knownCompositions: MutableList<ControlledComposition>Recomposer: _knownCompositionsCache: List<ControlledComposition>? assigned a clone on mutationthis.performInitialMovableContentInserts(composition); returns on throwcomposition.applyChanges()composition.applyLateChanges()(CompositionImpl as ControlledComposition)::recordReadOfoverride fun recordReadOf(value: Any): Unit
this.areChildrenComposing; nop if notified of a read from a child compositionthis.composer.currentRecomposeScope is null; aliased scope: RecomposeScopeImpl otherwise
this.composer.invalidateStack: Stack<RecomposeScopeImpl>; populated in
ComposerImpl::recomposeToGroupEndComposerImpl::addRecomposeScope
RecomposeScopeImpl(composition as CompositionImpl)scope.used = truescope.recordRead(value); as part of tracking the value, the composition records the read in the current scope: RecomposeScopeImpl
true if already recorded → already tracked by the compositionfun recordRead(instance: Any): Boolean
RecomposeScopeImpl method that records a read of the instance; invalidated on changecurrentToken: Int each time a composition starts composing in the recompose scopeinstance to the currentToken: Int at the time of the readfalse if (this: RecomposeScopeImpl).rereading since resetRecomposeScopeImpl::trackedInstances: MutableObjectIntMap<Any>? = null lazily assigned a !null instance
Int values.put(key: K, value: Int, default: Int): Int
key to the valuekey was mapped to, or the default if new.put(instance, this.currentToken, default = -1) equals this.currentToken: Int
true if instance was previously mapped to this.currentTokenthis.observer()?.onReadInScope(scope, value)
private fun observer()
: CompositionObserver?
= this.observerHolder.current()
CompositionObserver registered with compositions & have callbacks invoked by the respective compositionsvalue has been read in a composition if StateObjectImpl for optimisation purposes
internal abstract class StateObjectImpl: StateObject; instantiated by factories such as mutableStateOfStateObjectImpl::readerKind: AtomicInt
StateObjectImpl::recordReadIn(reader: ReaderKind): UnitStateObjectImpl::isReadIn(reader: ReaderKind): Booleanthis.observations.add(value, scope); adds scope to the population of recompose scopes that the value is read in
ScopeMap<K: Any, V: Any> wraps a MutableScatterMap<Any, Any>
K key accumulates a population of V elements; first mapped to an element then a setthis.observations: ScopeMap<Any, RecomposeScopeImpl>; state objects → the populations of recompose scopes they’re read inthis.derivedStates: ScopeMap<Any, DerivedState<*>>; state objects → the populations of derived states from itScopeMap::add calls MutableScatterMap::compute; the given key mapped to
V if there’s no existing entry or the key is already mapped to the elementMutableScatterSet<V> of every element the key’s been mapped toDerivedState<*> in this.derivedStates: ScopeMap<Any, DerivedState<*>>(CompositionImpl as ControlledComposition)::recordWriteOfoverride fun recordWriteOf(value: Any)
this::invalidateScopeOfLocked
value; invalidates all recompose scopes that read it
private fun invalidateScopeOfLocked(value: Any)
scope: RecomposeScopeImpl in the population that the value is mapped to in this.observations: ScopeMap<Any, RecomposeScopeImpl>scope.invalidateForResult(value) == InvalidationResult.IMMINENT
RecomposeScopeOwner::invalidate on the scope.owner: RecomposeScopeOwner? if !null
fun invalidate(scope: RecomposeScopeImpl, instance: Any?): InvalidationResult
(CompositionImpl as RecomposeScopeOwner)::invalidate on this call pathscope relayed the scope: RecomposeScopeImpl receiver of the invalidateForResult methodinstance relayed the value: Any? argument from the invalidateForResult methodscope.defaultsInvalid = true if scope.defaultsInScope; the default params also refreshedval anchor: Anchor? = scope.anchor; the slice in the slot table that the recompose scope corresonds toInvalidationResult.IGNORED if anchor == null || !anchor.valid
InvalidationResult.{IMMINENT | IGNORED} if !this.slotTable.ownsAnchor(anchor)
this; queries a delegate compositionval delegate: CompositionImpl? = synchronized(this.lock) { this.invalidationDelegate }
!null while inside this::delegateInvalidationsControlledComposition that’s not thisgroupIndex: Int that’s >= 0InvalidationResult.IMMINENT if delegate?.tryImminentInvalidation(scope, instance) == true
CompositionImpl::recordWriteOf run on each state write in a snapshot where the composition is composedtrue if the scope will be recomposed by the delegate within the current (before the next) composition passInvalidationResult.IGNORED otherwise; to be recomposed by neither this nor the delegateInvalidationResult.IGNORED if !scope.canRecompose; has opted out or is unable
this.invalidateChecked(scope, anchor, instance)
this.observer() unless InvalidationResult.IGNOREDval delegate: CompositionImpl?; initialised with
this.invalidationDelegate if this.slotTable.groupContainsAnchor(this.invalidationDelegateGroup, anchor)
this.invalidationDelegateGroup: Int = 0 assigned a different value while in this::delegateInvalidationstrue if (the slice corresponded to by) the recompose scope (as pointed to by the anchor) is in a movable contentreturn InvalidationResult.IMMINENT (recursion base) if this.tryImminentInvalidation(scope, instance)
CompositionImpl::recordWriteOf run on each state write in a snapshot where the composition is composedtrue if the scope will be recomposed by this within the current (before the next) composition passnull otherwise
this.invalidations.set(scope, ScopeInvalidated) if instance == null || instance !is DerivedState<*>
this.invalidations: ScopeMap<RecomposeScopeImpl, Any>; recompose scopes → the populations of outdated state objects they readScopeMap::set overrides and replaces the current population (if present) with the given elementinternal object ScopeInvalidated indicates that an invalidated recompose scope doesn’t track the population of outdated states it read (eg when RecomposeScopeImpl::invalidate has passed instance = null to RecomposeScopeOwner::invalidate)this.invalidations.add(scope, instance) if !this.invalidations.anyScopeOf(scope) { it === ScopeInvalidated }
this.invalidations doesn’t map the scope to ScopeInvalidated (→ unconditionally recomposed)DerivedState<*>; kept track of since derived states can avoid recomposition sometimesscopedelegate != null; returns delegate.invalidateChecked(scope, anchor, instance)this.parent.invalidate(this); notifies the CompositionContext
internal abstract fun invalidate(composition: ControlledComposition)
(Recomposer as CompositionContext)::invalidate on the call path from ComponentActivity::setContentcomposition to Recomposer::compositionInvalidations: MutableVector<ControlledComposition> if not already containedRecomposer::workContinuation: CancellableContinuation<Unit>? if eligible
suspend function needs a coroutine to run insuspend function call is delimited by the coroutine and reified for use in CPSsuspend function returns a T, the Continuation<T> (the rest of the coroutine) is resumed with a TInvalidationResult.DEFERRED if this.isComposingInvalidationResult.SCHEDULED otherwiseInvalidationResult.IGNOREDthis.observationsProcessed.add(value, scope) if unskipped
this.observationsProcessed: ScopeMap<Any, RecomposeScopeImpl>; state objects → the populations of recompose scopes that are recomposed in the current passvalue; invalidates all recompose scopes that read each