Edited at

Jetpack Composeについて調査 文法編


はじめに

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


注意点

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


Jetpack Composeの文法


Hello Worldを書く

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

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) }
)
}
}
}
}
}

これは以下のように表示されます。

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

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) }
)
}
}
}
}
}
}

これは以下のように表示されます。

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


Hello Worldの解説

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


setContent

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

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

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


+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をパラメータとして適用できる

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

@Composable

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

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

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を作ってみます。

タップされた回数を保持するのでステートを必要とします。

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 }
)
}
}

これは以下のように動作します



タップされた回数をカウントしていきます。上の例ではIntを使いましたが、@Modelを使うことで独自に定義した型を使うこともできます。独自の型を利用する形でCountableButtonコンポーネントを書き換えると以下のようになります。これは上記GIF画像と同じ動きをします。

@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

以下のようなコードは

Text(text = "Hello World!")

このようにも書けます。

<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