Jetpack Composeについて調査　文法編

# はじめに
　Google I/O 2019で発表された`Jetpack Compose`について調査しました。本記事ではその中でも特徴的な`宣言的UI`の構築を行うための文法についてまとめてみます。

## 注意点
本記事は5/21時点でpre-alpha段階の`Jetpack Compose`について調査を行なっており、今後の変更で大きくインターフェースが変わる可能性があります。

# Jetpack Composeの文法

## `Hello World`を書く
定番の`Hello World`は以下のように書けます。

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CraneWrapper {
                MaterialTheme {
                    val textColor = Color(0xFFFF0000.toInt())
                    Text(
                        text = "Hello World!",
                        style = +themeTextStyle { h2.copy(color = textColor) }
                    )
                }
            }
        }
    }
}
```
これは以下のように表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105287/666e413b-fb2f-398a-25ce-87afe08738f7.png" width=25%>

文字列を縦方向に二つ表示してみましょう。

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CraneWrapper {
                MaterialTheme {
                    val textColor = Color(0xFFFF0000.toInt())
                    Column {
                        Text(
                            text = "Hello",
                            style = +themeTextStyle { h2.copy(color = textColor) }
                        )
                        Text(
                            text = "World!",
                            style = +themeTextStyle { h2.copy(color = textColor) }
                        )
                    }
                }
            }
        }
    }
}
```
これは以下のように表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105287/014b91e6-cbc8-f455-d731-695107a63636.png" width=25%>

宣言的にUIが構築されているので、結構読みやすいです。

## `Hello World`の解説
上記`Hello World`に登場した`Jetpack Compose`固有のワードについて解説します。

### `setContent`
以下のように`Activity`の拡張関数として実装されています。

```kotlin
fun Activity.setContent(composable: @Composable() () -> Unit) =
    setContentView(FrameLayout(this).apply { compose(composable) })
```
受け取った`composable`な関数を`FrameLayout`に描画して自身にsetしています。
基本的にはこの関数を使うことで`Jetpack Compose`で書かれたUIを描画することになります。

### `CraneWrapper`
`Context`, `FocusManager`, `TextInputService`を作り、`Jetpack Compose`の世界でこれらを使えるようにするためのwrapperです。上記Android上でのUI構築に必須なものを作る部分なので、`setContent`直下にこれを記述し、これに関数を渡すようにUIを設計しないと例外が発生してしまいます。

### `MaterialTheme`
`Text`や`Button`など、各種コンポーネントに適用できるマテリアルデザインなスタイルを提供します。

#### `+themeTextStyle`
上記`MaterialTheme`で提供されている文字に関するスタイルです。これに文字のスタイルを定義する関数を渡すことができます。
この辺りは`KotlinDSL`の利点が活かされており、適切なスタイル定義をIDEやコンパイラの支援を受けつつ簡単に書くことができるようになっています。
上記コードでは、`h2`スタイルの色だけを`0xFFFF0000`(赤色)に変更するようにスタイルを定義しています。

### `Text`
文字列を表現する関数です。Androidで言う`TextView`に相当します。

#### `text`
この名前で渡した引数が`Text`で表示される文字列になります。

#### `style`
この名前で渡した引数が`Text`で表示される文字列のスタイルになります。

### `Column`
複数のコンポーネントを縦に並べて表示することができます。Androidのorientation=”vertical”な`LinearLayout`に相当します。

## コンポーネントを自作する
`Jetpack Compose`ではコンポーネントの自作も簡単に行うことができます。試しに上記Hello Worldを以下のような仕様でコンポーネントにしてみます。

- Hello World!と表示する
- styleをパラメータとして適用できる

これは以下のように実装することができます。

```kotlin
@Composable
fun HelloWorld(style: TextStyle? = null) {
    Text(
        text = "Hello World!",
        style = style
    )
}
```

このようにして作られたコンポーネントは、以下のようにして使うことができます。

```kotlin
class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CraneWrapper {
                MaterialTheme {
                    val textColor = Color(0xFFFF0000.toInt())
                    HelloWorld(
                        style = +themeTextStyle { h2.copy(color = textColor) }
                    )
                }
            }
        }
    }
}
```

コンポーネントの自作は非常に簡単で、`@Composable`を付与した関数を作るだけです。ただし、いくつか気をつけなければならない点もあり、

- 副作用を起こさないようにする
- 関数の引数として渡されたパラメータ以外は使わないようにする(例えばグローバル変数にアクセスしたりしない)

と行った注意点も存在します。

## `ステートを持つUI`を作る

上記`Hello World`はステートを持たないシンプルなUIでした。
今度は「ボタンをタップする度にカウントアップする」と言う機能を持ったUIを作ってみます。
タップされた回数を保持するのでステートを必要とします。


```kotlin
class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CraneWrapper {
                MaterialTheme {
                    CountableButton()
                }
            }
        }
    }
}

@Composable
fun CountableButton() {
    val count = +state { 0 }
    Column {
        Button(
            onClick = {
                count.value++
            },
            text = "Tap Me!!"
        )
        Text(
            text = "Tapped count ${count.value}",
            style = +themeTextStyle { h3 }
        )
    }
}
```

これは以下のように動作します
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105287/ef4124b9-25bb-1319-e8ee-2a28ffe471bb.gif" width=25%>
タップされた回数をカウントしていきます。上の例では`Int`を使いましたが、`@Model`を使うことで独自に定義した型を使うこともできます。独自の型を利用する形で`CountableButton`コンポーネントを書き換えると以下のようになります。これは上記GIF画像と同じ動きをします。

```kotlin
@Composable
fun CountableButton() {
    val count = +state { CountModel() }
    Column {
        Button(
            onClick = {
                count.value.increment()
            },
            text = "Tap Me!!"
        )
        Text(
            text = "Tapped count ${count.value.num}",
            style = +themeTextStyle { h3 }
        )
    }
}

@Model
class CountModel {
    var num = 0
    fun increment() {
        num++
    }
}
```

## `ステートを持つUI`の解説
上記`ステートを持つUI`に登場した`Jetpack Compose`固有のワードについて解説します。

### `+state`
この関数は`State`インスタンスを作るトップレベル関数です。
この関数には`State`インスタンスが保持するパラメータを初期化する関数を与えることができます。
上記初期化式で初期化されたパラメータを保持した`State`インスタンスを返します。

### `State`
stateを管理するクラスです。上記`+state`関数で作ることができます。
`value`プロパティを経由することで、内部で保持しているパラメータを参照、更新することができます。
このようにしてパラメータを更新すると、UIが再描画され、更新後のパラメータが適用されます。

### `@Model`
stateを表現するクラスに付与しなければならないアノテーションです。
`+state`関数で作成した`State`インスタンスで独自のクラスを保持させたい場合には、これを対象のクラスに付与しなければなりません。
これを付与し忘れた場合、コンパイルは通りますが、パラメータに変更があった際にUIが再描画されません。

## KTX

以下のようなコードは

```Kotlin
Text(text = "Hello World!")
```

このようにも書けます。

```Kotlin
<Text text="Hello World!" />
```

この`JSX`のタグにも似た書き方は`KTX`と呼ばれているそうです。この書き方はKotlinのコンパイラを拡張する方法で実現されているそうです。しかし、`KTX`は関数呼び出しで記述する方法を開発する以前の古い記法らしく、現在は`KTX`からそちらに移行している最中だそうです。今後のことを考えるとこの書き方はしない方が良さそうです。

# おわりに
`Jetpack Compose`について調べてみました。
Androidの既存の書き方(いわゆる宣言的ではない方法)だと色々と辛いところもありましたが、`Jetpack Compose`が実用化されると、非常に簡単かつ苦しみを少なくしてUIを構築できそうです。
`stable`になるまでまだまだ時間はかかりそうですが、非常に期待できそうです。

# 参考

https://developer.android.com/jetpack/compose
https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/compose/README.md
https://medium.com/q42-engineering/android-jetpack-compose-895b7fd04bf4