Jetpack ComposeでHello WorldのLayoutNodeが追加されるまでを読んでみるメモ

普通に以下のセッションがめちゃくちゃわかりやすいのでそれを見ることをおすすめします
Understanding Compose (Android Dev Summit '19)
https://www.youtube.com/watch?v=Q9MtlmmN4Q0

Understanding composeメモ
https://qiita.com/takahirom/items/e8fb7933fa44a546915f

# モチベーション
Jetpack ComposeのHello Worldは以下のような形になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/56e0e983-527a-1094-4299-b90532a20e00.png)


```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Greeting("Android")
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}
```

この`@Compose`で何が起こるのか。。？ 実際には馴染みのAndroidのViewがあるはず。。？`Greeting`や`Text`はUnitしかこれ返してないけど、どうやって追加してるの。。？ :thinking: 

0.1.0-dev02で確認していきます

デバッグのミスなどで間違っている可能性などはかなりあるので、何かあればご指摘ください :pray: 

# コードを見ていく

## LayoutNodeとは？

> 実際には馴染みのAndroidのViewがあるはず。。？

`setContent`の中は以下のようになっており、Composeは`AndroidComposeView`というViewに描画するようです。

```kotlin
fun Activity.setContent(
    content: @Composable() () -> Unit
): CompositionContext? {
    val composeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? AndroidComposeView
        ?: AndroidComposeView(this).also { setContentView(it) }  // **← ここでnewする！！**
```


AndroidComposeViewは以下のように`root`のレイアウトを持っています。

```kotlin
class AndroidComposeView constructor(context: Context) :
    ViewGroup(context), Owner, SemanticsTreeProvider, DensityScope {
    override var density: Density = Density(context)
        private set

    val root = LayoutNode().also { // **← rootのLayoutNode！！**
        it.measureBlocks = RootMeasureBlocks
    }
```

このrootのNodeにTextが追加されるようです。
ちなみにこのLayoutNodeクラスは`ComponentNode`のサブクラスになっており、`ComponentNode`は以下のように子codeを持ちます。

```kotlin
sealed class ComponentNode : Emittable {
    internal val children = mutableListOf<ComponentNode>()  // **← childrenを保持する！！**
```

## TextのLayoutNodeを追加しているのは誰か？

このchildrenの追加のタイミングにデバッガーを仕掛けてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/8794bccc-178d-63ac-8aef-7eb0a23f3626.png)
このようにTextが追加されているのがわかります。この`TextKt$Text$3$2$1$clidren...`、生成されている感があるので、気になります
この`instance`の値の出どころを探りましょう！

どうやらこのup()メソッドから渡されてくるようです。そして、_currentの値が渡されてくるようなので、`down()`の呼び元を調べます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/b3d721a8-90bc-a8aa-d83f-70447e07082a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/df413692-1886-6b6c-f9f2-e17d4bd3373a.png)


`down()`の呼び元としてemitNode()があるのですが、、**emitNode()を呼び出しているところがスタックトレースでは追えない部分が出てきます。**

## コンパイルされたコードの中から来るLayoutNode

コードを見ていくと実際にはViewComposer内の以下のような実装が適応されているようです。

```kotlin
    inline fun <T : View> emit(
        key: Any,
        /*crossinline*/
        ctor: (context: Context) -> T,
        update: ViewUpdater<T>.() -> Unit
    ) = with(composer) {
        startNode(key)
        // **ここにemitNodeがある**
        val node = if (inserting) ctor(context).also { emitNode(it) } 
        else useNode() as T
        ViewUpdater<T>(this, node).update()
        endNode()
    }
...
    @Suppress("PLUGIN_WARNING")
    inline fun call(
        key: Any,
        /*crossinline*/
        invalid: ViewValidator.() -> Boolean,
        block: @Composable() () -> Unit
    ) = with(composer) {
        startGroup(key)
        if (ViewValidator(composer).invalid() || inserting) {
            startGroup(invocation)
            block()
            endGroup()
        } else {
            skipCurrentGroup()
        }
        endGroup()
    }
```

そこでコンパイラのコードをちょっと見ると以下のようなコメントを発見できます。
https://android.googlesource.com/platform/frameworks/support/+/a5396b43ef905756b18805fe0ae31a99e96e6df6/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt#239

つまり**`Foo(text="foo")` を `composer.call()`に変換しています**


```kotlin
        /*
        Foo(text="foo")
        // transforms into
        val attr_text = "foo"
        composer.call(
            key = 123,
            invalid = { changed(attr_text) },
            block = { Foo(attr_text) }
        )
         */
```

つまり最初のMainActivityのコードは @Composeをなくして以下のように書くことができます。

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Greeting("Android")
            }
        }
    }
}

//@Composable
//fun Greeting(name: String) {
//    Text(text = "Hello $name!")
//}

fun Greeting(name: String) {
    composer.call(
        key = 0x9a8fdf7d,
        invalid = { changed(name) },
        block = { Text(text = "Hello $name!") }
    )
}
```

> `Greeting`や`Text`はUnitしかこれ返してないけど、どうやって追加してるの。。？

これと同じことがText()の中でも行われ、そこでcomposable.emit()に変換され、**`emitNode()`を呼んでいるようです。**

# デバッグ方法のメモ

公開されているコードなのですが、かなりinline化されていてデバッガーで追いにくいです。またAndroid StudioについているKotlinのJavaへのデコンパイラではラムダを埋め込んでくれないので実質追えません。
brew cask install jadなどでjadをインストールして
./gradlew compileDebugKotlin
などをした後に

```
cd app/build/tmp/kotlin-classes/debug/[パッケージ]
jad *.class
```

することでラムダを埋め込んだコードを出してくれました。

# まとめ
多分変更が何度も入っていくだろうとは思うので一瞬でこの知識は使えなくなるとは思いますが、ソースコードを追えるようになってきました。