How is `Modifier` implemented such that `this` or ...
# compose
k
How is
Modifier
implemented such that
this
or
it
does not need to be passed to it or any of its methods? As the only way I can think of is as a command list builder or similar As
Modifier
seems to immediately be a very powerful tool to implement parameters and such with due to its simplicity and everything that can be modified is in a single class so it is easy to find a parameter of interest instead of having to search through lots of additional methods and such that might not be related to parameters at all
c
You can always inspect the Modifier method, that's how I made a new modifier. Just basically copied one that was done already 🙂
k
Unfortunately I do not have Compose installed at the moment so i cannot inspect the method I can only go by Jetpack Compose source code and this slack for info
c
What?
Do you have any impairing that impact you creating a compose project?
a
Extension functions always have a implicit
this
which is the receiver.
k
Hmm so, Modifier would be an extension class of Composable ? In which Modifier implicitly receives the composable instance as
this
?
@Cicero no, I thought that I would be able to get a quick answer on how Modifier is implemented
c
I just feel its really fun to navigate trough the API and see how some things were implemented. I will provide you with an extension I wrote
k
I just feel its really fun to navigate trough the API and see how some things were implemented.
🙂
c
An example of how .size() was implemented and how you could extend it:
Copy code
@Stable
fun Modifier.size(size: Dp) = this.then(
    SizeModifier(
        minWidth = size,
        maxWidth = size,
        minHeight = size,
        maxHeight = size,
        enforceIncoming = true,
        inspectorInfo = debugInspectorInfo {
            name = "size"
            value = size
        }
    )
)

fun Modifier.fixedSize(fixedSize: Dp = 100.dp) = this.then(
    other = Modifier.size(fixedSize)
)
This "then" expects you to pass in a Modifier
this is the Modifier implementation:
Copy code
interface Modifier {

    /**
     * Accumulates a value starting with [initial] and applying [operation] to the current value
     * and each element from outside in.
     *
     * Elements wrap one another in a chain from left to right; an [Element] that appears to the
     * left of another in a `+` expression or in [operation]'s parameter order affects all
     * of the elements that appear after it. [foldIn] may be used to accumulate a value starting
     * from the parent or head of the modifier chain to the final wrapped child.
     */
    fun <R> foldIn(initial: R, operation: (R, Element) -> R): R

    /**
     * Accumulates a value starting with [initial] and applying [operation] to the current value
     * and each element from inside out.
     *
     * Elements wrap one another in a chain from left to right; an [Element] that appears to the
     * left of another in a `+` expression or in [operation]'s parameter order affects all
     * of the elements that appear after it. [foldOut] may be used to accumulate a value starting
     * from the child or tail of the modifier chain up to the parent or head of the chain.
     */
    fun <R> foldOut(initial: R, operation: (Element, R) -> R): R

    /**
     * Returns `true` if [predicate] returns true for any [Element] in this [Modifier].
     */
    fun any(predicate: (Element) -> Boolean): Boolean

    /**
     * Returns `true` if [predicate] returns true for all [Element]s in this [Modifier] or if
     * this [Modifier] contains no [Element]s.
     */
    fun all(predicate: (Element) -> Boolean): Boolean

    /**
     * Concatenates this modifier with another.
     *
     * Returns a [Modifier] representing this modifier followed by [other] in sequence.
     */
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)

    /**
     * A single element contained within a [Modifier] chain.
     */
    interface Element : Modifier {
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R =
            operation(this, initial)

        override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this)

        override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this)
    }

    /**
     * The companion object `Modifier` is the empty, default, or starter [Modifier]
     * that contains no [elements][Element]. Use it to create a new [Modifier] using
     * modifier extension factory functions:
     *
     * @sample androidx.compose.ui.samples.ModifierUsageSample
     *
     * or as the default value for [Modifier] parameters:
     *
     * @sample androidx.compose.ui.samples.ModifierParameterSample
     */
    // The companion object implements `Modifier` so that it may be used  as the start of a
    // modifier extension factory expression.
    companion object : Modifier {
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
        override fun any(predicate: (Element) -> Boolean): Boolean = false
        override fun all(predicate: (Element) -> Boolean): Boolean = true
        override infix fun then(other: Modifier): Modifier = other
        override fun toString() = "Modifier"
    }
}
Hope this is enough to spark your curiosity and gets you creating your own proj. Its really simple and it will be much more rewarding than asking here
k
Hmm so it just extends the bass Modifier class with extension functions foldIn, all, any, and so on?
c
jap, nothing spaceship worthy of complexity but you can do some crazy stuff
k
As I don't see any input for variables such as a composable object so I am not sure how it magically works
c
Well, we all believe on it. (Excuse the fairy pun)
I can't find any obvious reference to an explanation so I kind of am Interested in a better answer for "How does modifiers work"
k
Is everything done inside drawWithCache?
c
Man, this might shine a light on you as Adam Powell tries to describe how Modifiers work: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1607996900279600
k
For example, something like
Copy code
Modifier offset(x : int, y : int) {
    return this.andThen(Modifier.drawWithCache {
        setOffset(x, y)
    }
}
c
Did my example just vanished?
Weird, more like:
Copy code
@Composable
fun Modifier.TestOffset(x : Dp, y : Dp) = this.then(Modifier.offset(x,y))
k
hmm ok, I meant like, might that be how Modifier.offset itself can be implemented, but thanks 🙂
c
Got it, I will leave you to your curiosity now :)
z
As I don't see any input for variables such as a composable object so I am not sure how it magically works
There’s no such thing as a composable object. The modifier chain is eventually associated with the LayoutNode that gets emitted by the composition, and then it can be read by the layout and other machinery. https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt;l=84;drc=d1bb6b866401d0c873b942fbfaab53289e88a174
❤️ 2
k
Thanks
So the Modifier represents a callback chain, which is added to the onPositioned callbacks in LayoutNode.kt upon setting the Modifier value
z
Modifiers basically just form a linked list of arbitrary objects - not all are callbacks. Compose’s runtime walks that list to find out what modifiers are there. So yea, the layout code can look for all instances of onGloballyPositioned modifiers and add them as callbacks to the layout nodes, while ignoring other modifier types.
k
Ok 🙂
Thanks
t
@Cicero What’s
debugInspectorInfo
? 🤔
c
No clue, I was just pasting code from the Compose source and then sharing how to extend it 🙂
z
It avoids allocating stuff for the InspectorInfo dsl if inspection is disabled
💪🏽 1