Jetpack Composeを使ってみた

# はじめに
今回は，個人的に__Jetpack Compose__について調べたことをまとめてみました．
この記事では，__AOSP（Android Open Source Project）__をダウンロードし，その中に自分でいじる用のActivityを作成して，Jetpack Composeを利用しています．
さらに，簡単なアプリをもとにして既存のプログラムと比較しています．

# AOSP（Android Open Source Project）のダウンロード
現状では(2019/7/15時点では)，ライブラリとして提供されていないのでAOSPをダウンロードしてJetpack Composeのソースコードを確認しつつ，実行していこうと思います．
ダウンロードの手順は公式が出してくれているので，[こちら](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/README.md)で確認していただければと思います．

# そもそもJetpack Composeとは
[Google I/O 2019のKeynote](https://www.youtube.com/watch?v=LoLqSbV1ELU&t=2710s)で発表された__Android用のUIツールキット__です．
Jetpack Composeを使うことで，宣言的にUIの構築を行うことができます．

# 既存コードとの比較
ここから，実際に既存のコードとJetpack Composeを利用したコードを比較していきます．
Android側のコードは，Kotlinで書いています．

## 初級編
まずは，簡単なプログラムで比較をしていきます．

新規プロジェクトとして立ち上げた際のコードで比較してみます．

以下のような，画面中央に`Hello World!`と表示するだけのプログラムです．


|<img width="250" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/dc0b4abc-9a10-d379-80e7-d0750c37688d.png">|
|:-:|


### 既存コード
こちらのKotlinファイルでは，XMLファイルを表示しています．

```{kotlin}
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
```

さらに，こちらのXMLファイルでは，`Hello World!`を表示させるためのTextViewの配置を行なっています．

```{xml}
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

このように，XMLファイルとKotlinファイルを利用して画面を作成しています．

### Jetpack Compose
ここからは，先ほどの画面をJetpack Composeを利用して作成してきます．

まず，`@Composable`をつけた関数の中にコンポーネントを配置していきます．

`MaterialTheme`は今までのStyleに値するコンポーネントとなっています．(ここでは，無くても良いですが...)

今回は，画面の中央に`Hello World!`を表示させたいので，`Center`の中に`Text`を配置していきます．
`text = "Helloworld!"`の部分では，表示する文字列を設定しています．
今回に関しては，無くてもいいのですが，説明用に`style = TextStyle`を記述しています．
この中では，フォントやフォントサイズなどの設定ができます．


```{kotlin}
class MyComposeActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { CraneWrapper { MyApp() }}
    }

    @Composable
    fun MyApp() {
        MaterialTheme {
            Center {
                Text(text = "Hello World!", style = TextStyle(
                fontSize = 14.sp, fontFamily = FontFamily("sans-serif")
                )
                )
            }
        }
    }
}
```
このように，`Hello World!`を表示させるだけでもここまで違いが出ています．
結構違いますよね！
この表示だけでもここまでの違いが出たのは，自分でも驚きました笑

## 応用(?)編
先ほどのプログラムは，`Hello World!`を表示するだけのプログラムでしたが，次は少しだけ処理を加えてみたいと思います.

簡単なカウントアプリを作成します．
`COUNT UPボタン`を押すことでカウントが1増える，`COUNT DOWNボタン`を押すことでカウントが1減る，`COUNT RESETボタン`を押すことでカウントが0になる，というような簡単なプログラムです．

以下のような，画面のアプリとなります．

|<img width="250" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/8e9c141b-3882-b3ed-4b6d-43af283cee96.png">|
|:-:|


### 既存コード
こちらのKotlinファイルでは，XMLファイルを表示しています．
さらに，ボタンを押した時の処理として`count`の値の変更をし，TextViewの更新をしています．

※書き方はいろいろあると思いますが，自分の技術力と時間的にこのようなコードとなっています．

```{kotlin}
class MainActivity : AppCompatActivity() {

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var count = 0

        countUp.setOnClickListener {
            count++
            countText.text = "${count}回"
        }

        countDown.setOnClickListener {
            if (count > 0) count--
            countText.text = "${count}回"
        }

        countReset.setOnClickListener {
            count = 0
            countText.text = "${count}回"
        }
    }
}
}
```


さらに，こちらのXMLファイルでは，カウントを表示させるようのTextViewとカウントを変更させるようのButtonの配置をしています．(個人的に`ConstraintLayout`が好きなので乱用しています)

```{xml}
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent" 
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="8dp" 
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent">

        <TextView
                android:id="@+id/countText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0回"
                android:textSize="36sp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
        <Button
                android:text="count up"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/countUp"
                style="@android:style/Widget.Material.Button.Colored"
                app:layout_constraintTop_toBottomOf="@+id/countText"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginTop="104dp"/>
        <Button
                android:text="count down"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" 
                android:id="@+id/countDown"
                style="@android:style/Widget.Material.Button.Colored"
                android:layout_marginTop="16dp"
                app:layout_constraintTop_toBottomOf="@+id/countUp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
        <Button
                android:text="count reset"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" 
                android:id="@+id/countReset"
                style="@android:style/Widget.Material.Button.Colored"
                android:layout_marginTop="16dp"
                app:layout_constraintTop_toBottomOf="@+id/countDown"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```

説明の必要はなさそうですが，こんな感じで既存のコードではカウントアプリを作成します．

### Jetpack Compose
Jetpack Composeを使うとこんな感じのコードになります．

`Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly)`は等間隔にコンポーネントを配置できるようです．

`Text`の説明は先ほどとあまり変わりませんね．`var count = +state { 0 }`のような感じで変数宣言してあげて，`${count.value}回`としてあげると，count.valueが更新されるたびに自動で更新されるようです．すごい！

本当はButtonの配置の際に，`Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly)`を2重で適用させたかったのですが，うまくできなかったため`HeightSpacer(16.dp)`でMarginを空けています．

`Button`は`Text`の時と同じく，`text = "count up"`のような感じで文字列の設定ができます．さらに，`onClick = {count.value++}`のような感じで，そのButtonが選択された時の処理を記述できます．このプログラムは単純な処理だったのでそのまま記述していますが，もちろん関数を挿入することもできます．


```{kotlin}
class MyComposeActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { CraneWrapper { MyApp() }}
    }

    @Composable
    fun MyApp() {

        var count = +state { 0 }

        MaterialTheme {
            Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
                Text(text = "${count.value}回", style = TextStyle(fontSize = 36.sp))

                Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
                    Button(onClick = {count.value++}, text = "count up")

                    HeightSpacer(16.dp)

                    Button(onClick = {
                        if(count.value > 0) count.value--
                    }, text = "count down")

                    HeightSpacer(16.dp)

                    Button(onClick = {count.value = 0}, text = "count reset")
                }
            }
        }
    }
}
```
たったこれだけで，同じ画面と処理のプログラムを書くことができました．
__えっこれだけ？マ？__
これが正直な感想でしたw

既存のコードの書き方が多少汚いにしろ，ここまでシンプルなコードになるんですね．
ただ，他のライブラリを使えば既存のコードでもシンプルになるとは思いますが...

# 使ってみた感想
今回は，AOSPをダウンロードしてJetpack Composeを使ってみました．発表されてからいじったことがなかったので，いくつかのサイトとAOSPのコードを参考にして記述してみました．そのため，完全に理解しきれていない部分も多く，かなり時間がかかってしまいました．また，そこまで凝ったようなプログラムを作成できるまで慣れていないため，簡単なプログラムでの比較となってしまいました．

作成したプログラムはどちらも簡単なものでしたが，思っていたよりもシンプルなコードになった印象でした．一方で，今までの書き方と異なるため，書き慣れるまで少し時間がかかりそうです．さらに，今までXMLに配置してからDesignタブで確認していた身としては，この書き方でどのようなUIになるのか確認できないのは少し不便に感じました．

そもそも，Jetpack Composeはまだライブラリとして提供されていないので，今後もコンポーネントの追加はされていくと思います．追加されることで，よりUIの幅が広がっていきますね．どの程度のコンポーネントまで追加されるのかとても楽しみです．pre版でもライブラリとして提供されれば，もっと触れる機会は増えるのかなとは思います．

今年に入り，Jetpack ComposeとSwiftUIが発表され，今後はAndroid・iOSアプリ開発ではこのようなツールを利用してUIの構築を行なっていくことになりそうです．なので，今のうちから軽く触れていても良いツールかなと感じました．

# 参考リンク
Jetpack Compose: https://developer.android.com/jetpack/compose
Android Jetpack: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/README.md
Google I/O 2019 Keynote: https://www.youtube.com/watch?v=LoLqSbV1ELU&t=2710s
Jetpack Composeについて調査　文法編: https://qiita.com/shyne/items/d149f4ccde308d7019af
Jetpack Composeを利用したコンポーネント作成からレイアウト生成までの流れ: https://qiita.com/tkhs0604/items/de804fe7a477fbd394e8
SwiftUI: https://developer.apple.com/jp/xcode/swiftui/