In JetPack Compose, you’ll often use Modifier to control the behavior and appearance of components such as size, background, etc. You can also add interactions such as clicking, swiping, etc. The Modifier function is very powerful. The Modifier core code is less than 100 lines, the Modifier Kotlin advanced function with very 6, so that the initial look at the code a little meng ๐Ÿ˜ต. So I plan to write this article to learn how to perform the Modifier code. Modifier core source code as follows Modifier.kt

interface Modifier {
    fun <R> foldIn(initial: R, operation: (R.Element) - >R): R

    fun <R> foldOut(initial: R, operation: (Element.R) - >R): R

    fun any(predicate: (Element) - >Boolean): Boolean
    
    fun all(predicate: (Element) - >Boolean): Boolean

    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)

    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)}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(a) = "Modifier"}}class CombinedModifier(
    private val outer: Modifier,
    private val inner: Modifier
) : Modifier {
    override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)

    override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)

    override fun any(predicate: (Modifier.Element) - >Boolean): Boolean =
        outer.any(predicate) || inner.any(predicate)

    override fun all(predicate: (Modifier.Element) - >Boolean): Boolean =
        outer.all(predicate) && inner.all(predicate)

    override fun equals(other: Any?).: Boolean =
        other is CombinedModifier && outer == other.outer && inner == other.inner

    override fun hashCode(a): Int = outer.hashCode() + 31 * inner.hashCode()

    override fun toString(a) = "[" + foldIn("") { acc, element ->
        if (acc.isEmpty()) element.toString() else "$acc.$element"
    } + "]"
}

Copy the code

The code structure in the Modifier. Kt file is as follows

Element Provides an abstract interface for the Modifier Element. Modifier ** provides a static singleton class that implements the Modifier interface. CombinedModifier will be two Modifier combination, is an important key to generate the Modifier chain.

I’m going to focus on the then foldIn foldOut three functions

fun  foldIn(initial: R, operation: (R.Element) - >R): R

fun <R> foldOut(initial: R, operation: (Element.R) - >R): R

infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)
Copy the code

To facilitate the explanation, provide a simplified version of the Modifier implementation classes and some functions.

class SizeModifier : Modifier.Element {
    override fun toString(a) = "SizeModifier"
}

class PaddingModifier : Modifier.Element {
    override fun toString(a) = "PaddingModifier"
}

class OffsetModifier : Modifier.Element {
    override fun toString(a) = "OffsetModifier"
}

fun Modifier.size(a) = this.then(SizeModifier())
fun Modifier.padding(a) = this.then(PaddingModifier())
fun Modifier.offset(a) = this.then(OffsetModifier())
Copy the code

Then the function

The then method is an important function to generate the Modifier chain.

fun main(a) {
    valModifier = modifier. The size (). The padding (). The offset () println (modifier)} output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [SizeModifier, PaddingModifier, OffsetModifier]Copy the code

Below the Modifier.size().padding().offset() single step to break the Modifier generation chain process

The first step

val modifier1 = Modifier
Copy the code

โš ๏ธ This Modifier is a static singleton Modifier, not an interface.

The second step

val modifier2 = modifier1.size()
Copy the code

According to the size function defined above, =>

val modifier2 = Modifier.then(SizeModifier())

Then according to the static singleton Modifier then function

  companion object : Modifier {
        override infix fun then(other: Modifier): Modifier = other
    }
Copy the code

Modifier2 =SizeModifier() val Modifier2 =SizeModifier(

The third step

val modifier3 = modifier2.padding()
Copy the code

PaddingModifier() => Modifier3 =modifier2.then(PaddingModifier())

Modifier2 is the SizeModifier object, which implements Modifier.Element. That is to perform the Modifier interface default then function.

  infix fun then(other: Modifier): Modifier =
      if (other === Modifier) this else CombinedModifier(this, other)
Copy the code

SizeModifier = PaddingModifier = SizeModifier = PaddingModifier = SizeModifier = PaddingModifier

=> modifier3 = CombinedModifier(modifier2, PaddingModifier())

=> modifier3 = CombinedModifier(SizeModifier(), PaddingModifier())

The fourth step

val modifier4 = modifier3.offset()
Copy the code

This step is similar to modifier3, so the

//modifier4 =>
CombinedModifier(outer = modifier3, inner = OffsetModifier())
//modifier4 => 
CombinedModifier(outer = CombinedModifier(modifier2, PaddingModifier()), inner = OffsetModifier())
//modifier4 =>
CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())
Copy the code

Modifier4 finally gets something like a Russian doll CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())

Here are two graphs to summarize

FoldOut function

With the chain analysis above, let’s talk about how to traverse the elements of the chain. The foldOut function is given an initial value, then evaluates and returns the value on the Modifier chain according to the operation logic we passed in.

fun main(a) {
    val modifier = Modifier.size().padding().offset()
    val result = modifier.foldOut("start---") { mod, str ->
        println(mod)
        "$str$mod "
    }
   print("result=$result")} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- OffsetModifier PaddingModifier SizeModifier result = start - OffsetModifier PaddingModifier SizeModifierCopy the code

Magically, foldOut can pass through all the elements on the Modifier chain. How does it do this? Let’s change the above code for easy analysis

fun main(a) {
    // ๐Ÿ‘‡ code 0๏ธ one
    val modifier = Modifier.size().padding().offset()
    val initial="start---"
    val operation= { mod:Modifier, str:String ->
        println(mod)
        "$str$mod "
    }
    val result= modifier.foldOut(initial,operation)
    print("result=$result")
Copy the code

According to the then function of the Modifier above, we know how it forms the chain. From the above analysis, we know that code 0๏ธ retail modifier is the type of CombinedModifier, and its form is as follows Figure 2-1 ๐Ÿ‘†

Now that you know is CombinedModifier that look at its foldOut is how to achieve it

override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R =
    outer.foldOut(inner.foldOut(initial, operation), operation)
Copy the code

This looks like a very simple two lines of code, but it’s a lot of work, so let’s transform this function.

  override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
       // execute inner foldOut first
        val nextInitial:R=inner.foldOut(initial, operation)
        Inner foldOut = initial; outer foldOut = initial
        return outer.foldOut(nextInitial, operation)
   }
Copy the code

The first step is to execute the Inner foldOut function. As shown in Figure 2-1, the inner modifier is the object of the OffsetModifier type. OffsetModifier finally inherits Modifier.Element, whose foldOut function is implemented as follows

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

This function calls the operation function we passed in and takes the current object and the intial passed in as arguments. This is the first time the code we wrote in the operation body executesAccording to the above analysis, the first step is knownย val nextInitial:R=inner.foldOut(initial, operation)The return value is the return value of operation, which is"``$``str``$``mod ``"The value here is zerostart---OffsetModifier Once the parameters are ready, we are ready to execute step 2, which executes outer’s foldOut function, as shown in Figure 2-1Outer is still of CombinedModifier type, its foldOut function is still the first step to execute its inner foldOut function, and its inner is the SizeModifer object. It is still of type Modifier.Element, like above, when the code in the operation body is executed a second timeNow it’s time to execute its Outer foldOut, whose outer is the SizeModifer object, which is the Modifier.Element, so the code in the operation body is executed a third time.

The foldOut execution flow is illustrated with a piece of pseudocode

//0๏ธ retail CombinedModifier foldOut function we decompose into two steps
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
    // execute inner foldOut first
    val initial1:R=inner.foldOut(initial, operation)
    Inner fodOut = foldOut; outer foldOut = foldOut
    return outer.foldOut(initial1, operation)
}
//1๏ธ inner iF Modifier.Element type,
// Inner foldOut executes operation
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
    // Operation is executed
    val initial1:R=operation(inner,initial)
    return outer.foldOut(initial1, operation)
}
//2๏ธ discontinuation of outer's foldOut, if it is still CombinedModifier, repeat the above steps
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
    val initial1:R=operation(inner,initial)
    // Assume that outer can access its inner and outer directly
    val initial2=outer.inner.foldOut(initial1, operation)
    return outer.outer.foldOut(initial2, operation)
}

//3๏ธ if outer. Inner or Modifier.Element type, the same
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
    val initial1:R=operation(inner,initial)
    val initial2:R=operation(outer.inner,initial1)
    return outer.outer.foldOut(initial2, operation)
}
//4๏ธ one until finally OUTER is not CombinedModifier type end
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
    val initial1:R=operation(inner,initial)
    val initial2:R=operation(outer.inner,initial1/* The return value of the previous step */)...valinitialN:R=operation(outer.outer....... outer.inner,initialN_1/* The return value of the previous step */)
    returnoperation(outer.outer......... outer,initialN) }Copy the code

Conclusion: The CombinedModifier foldOut function executes its inner foldOut function and then outer’s foldOut function, taking the foldOut return value of inner as an argument to outer’s foldOut function

FoldIn function

The foldIn function is similar to the foldOut function in that it requires an initial value and an operation function and returns the result according to the Modifier chain. But there are some differences. Here is a simple example of foldIn

    val modifier = Modifier.size().padding().offset()
    val result = modifier.foldIn("start----") { str, mod ->
        println(mod)
        "$str$mod "
    }
    print("result=$result") -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- SizeModifier PaddingModifier OffsetModifier result = start - SizeModifier PaddingModifier OffsetModifierCopy the code

Code 3-1๐Ÿ‘† You can see from the output that foldIn is executed in the reverse order of foldOut. FoldIn function of CombinedModifier

    override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)
Copy the code

Again, this function can be decomposed into two steps

   override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R{
        //1. Execute outer's foldIn function
        val result1=outer.foldIn(initial, operation)
        Outer's foldIn function returns the foldIn of inner
        val result0=inner.foldIn(result1, operation)
        return  result0;
    }
Copy the code

For code 3-1 we can expand it based on the function above

    //val modifier = Modifier.size().padding().offset()
	//๐Ÿ‘† is equivalent to => ๐Ÿ‘‡
	val modifier1 = Modifier.size()
    val modifier2 = CombinedModifier(inner = PaddingModifier(), outer = modifier1)
    val modifier3 = CombinedModifier(inner = OffsetModifier(), outer = modifier2)

	//val result = modifier.foldIn("start----"){.... }
    //๐Ÿ‘† is equivalent to => ๐Ÿ‘‡
    val initial = "start---"
    val operation = { str: String, mod: Modifier ->
        println(mod)
        "$str$mod "
    }
    / / 0 ๏ธ โƒฃ
    modifier3.foldIn(initial, operation)
Copy the code

According to the foldIn function of CombinedModifier, code 0๏ธ is equivalent to

    / / 1 ๏ธ โƒฃ
	val result1=modifier2.foldIn(initial,operation)
    
    val result0=OffsetModifier().foldIn(result1,operation)
    return result0
Copy the code

Because modifier2 is still code CombinedModifier type, code 1๏ธ is equivalent to

	val result2=modifier1.foldIn(initial,operation)
    
    val result1=PaddingModifier().foldIn(result2,operation)
    
    val result0=OffsetModifier().foldIn(result1,operation)
    return result0
Copy the code

Code 3-2๐Ÿ‘† modifier1 is the SizeModifier object, which is of type Modifier.Element.Element’s foldIn function is as follows

        override fun <R> foldIn(initial: R, operation: (R.Element) - >R): R =
            operation(initial, this)
Copy the code

This function simply executes the operation function we passed in. PaddingModifier and OffsetModifier are also of the Modifier type.Element type, so codes 3-2 ** are equivalent to

    val result2 = operation(initial, SizeModifier())
    val result1 = operation(result2, PaddingModifier())
    val result0 = operation(result1, OffsetModifier())
    return result0
Copy the code

As you can see, foldIn eventually executes the operation function we passed in sequence along the Modifier chain. The result of each operation is used as the initial parameter of the next operation function.

conclusion

Modifier Combines the two modifiers into a CombinedModifier using the THEN function, inner pointing to the Modifier corresponding to the current node, outer pointing to the Modifier corresponding to the next node, If outer is also a CombinedModifier, then the Modifier can be extended.

FoldOut & foldIn Similarity: Given an initial value, return a calculated value. Each element on the execution Modifier chain is traversed. Difference: The traversal sequence of the two functions is different. FoldOut is executed from back to front in the sequence of adding the Modifier chain, while foldIn is executed from front to back in the sequence of adding the Modifier chain.