はじめに
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
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をパラメータとして適用できる
これは以下のように実装することができます。
@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