フロントエンドエンジニア(React)からみたJetpack Compose
はじめに
同期に脅され薦められてJetpack Composeに触れたのでその所感をまとめてます。
ちなみに自分自身は
- 学生時代にAndroidアプリ作るの教えてたし、2つほどアプリをリリースした
- Kotlin + MVVM + RxJava(Kotlin?)でアプリ作ったことある(3年ほど前)
- Javaの時代を知っている
- FragmentのNavigationまでは知ってた
- iOSも少し書いた(ほとんど忘れた)
- Swiftもうほぼ書けない(忘れた)
だったので、モバイルアプリエンジニアではないです。
Jetpack Composeで遊んでみる
React自体の説明は公式のチュートリアルがあるのでそれを見てください。
ここではJetpack Composeを実際に使ってみます。今回遊びで作ったものは以下です。
頑張ってMVVM + LiveDataとかもやってますがそこは省略します。
リポジトリはjiko21/TodoComposeです
Componentを実装する
まずは単純に与えられたデータを表示するComponentを実装してみましょう。 以下はタスクを表示するリストアイテムです。
カードのようなcontainer内にテキストとボタンがありますがこれをJetpack Composeで書くと次のようになります。
@Composable
fun ListItem(task: Task, onDelete: () -> Unit) {
val padding = 16.dp
Card(
backgroundColor = colorResource(R.color.background),
modifier = Modifier
.border(
width = 0.dp,
color = Color.Transparent,
shape = RoundedCornerShape(10.dp),
)
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(padding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = task.content, fontSize = 24.sp)
Button(
onClick = onDelete,
colors = ButtonDefaults.textButtonColors(
backgroundColor = colorResource(id = R.color.danger),
contentColor = Color.White,
)
) {
Text("Delete")
}
}
}
}
ReactのFunction Componentと同様、引数を持ってますね。ここはprops
に対応します。
今回はTask
とイベントハンドラを引数に取ってます。
イベントハンドラに関しては、React同様、単純な関数を引数にしており、利用したい際も、
Button(onClick = onDelete)
のように指定するだけなのでほぼ一緒です。
また、padding
なる変数がありますが、これはReactの内部で普通に宣言される変数と同じようなものです。
では実際にViewを作る部分にフォーカスしてみましょう。
Card
├── Text
└── Button
というような階層構造になってますね。ここに関してはReactの描くDOM Treeと同じです。 これらの要素はJetpack Composeが標準で用意してくれているComposeです。
また、気になった方もいるかと思いますが、Button
の中にText
がありますね。
Button(
onClick = onDelete,
colors = ButtonDefaults.textButtonColors(
backgroundColor = colorResource(id = R.color.danger),
contentColor = Color.White,
)
) {
Text("Delete")
}
Reactでいうchildren
みたいなものだと思ってください。
最後に、Kotlinに慣れていない人に向けて補足しておくと、Kotlinでは最後の式が評価され、戻り値となります。(これはrubyやRustに近いですね)
なので、return
文を付け足すと
@Composable
fun ListItem(task: Task, onDelete: () -> Unit) {
val padding = 16.dp
return Card(
backgroundColor = colorResource(R.color.background),
modifier = Modifier
.border(
width = 0.dp,
color = Color.Transparent,
shape = RoundedCornerShape(10.dp),
)
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(padding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = task.content, fontSize = 24.sp)
Button(
onClick = onDelete,
colors = ButtonDefaults.textButtonColors(
backgroundColor = colorResource(id = R.color.danger),
contentColor = Color.White,
)
) {
Text("Delete")
}
}
}
}
これは実質Reactですね。
状態を定義する
とはいえ、このままだと単に与えられたprops
を表示するだけのFunctional Componentと変わりません。
ReactにはStateがありますがこれと同様のものがJetpack Composeにも存在します。
定義の仕方は簡単で次のように記述するだけです。
var isCreateModalOpen by remember {
mutableStateOf(false)
}
これだけみると単方向のData bindingのように見えるかもしれませんが、 byデリゲートがあるため、getterやsetterが省略できているだけです。 これがなければ、useStateと同様に、代入不可能な値とDispatcherが返却されます。
以下は実際にStateを利用した例です。isCreateModalOpen
という値でモーダルの開閉を制御しています。
@Composable
fun Container(viewModel: TaskViewModel) {
var isCreateModalOpen by remember {
mutableStateOf(false)
}
val lists = viewModel.tasks.observeAsState(arrayListOf())
Scaffold(
floatingActionButton = {
FloatingActionButton(
contentColor = Color.White,
onClick = { isCreateModalOpen = true }) {
Icon(Icons.Filled.Add, contentDescription = "add task")
}
},
content = {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
lists.value?.let {
TaskLists(lists = lists.value, onDelete = {id ->
viewModel.remove(id)
})
}
TaskCreateDialog(isCreateModalOpen,
onDismiss = { isCreateModalOpen = false },
onSubmit = { task ->
isCreateModalOpen = false
viewModel.insert(task)
},
onCancel = { isCreateModalOpen = false },
)
}
}
)
}
コンポーネントを確認しながら実装したい
フロントエンドエンジニアだと、実装しながらどのようなデザインか、を確認しながら実装したいかと思います。 こういったことも、Jetpack Composeで可能です。
例えば先ほど実装したListItemのデザインを確認したいなら、次のようなコードを付け足すだけで可能です。
@Preview(showBackground = true)
@Composable
fun PreviewListItem() {
ListItem(task = Task(1, "服を洗う"), {})
}
Storybookとは違い、Android Studioだけで完結するのでかなりわかりやすいです。
まとめ
関数ベースということでJetpack ComposeはReactに慣れている人だととっつきやすいですね。 もちろん、言語によってはトップレベルで関数を定義できないものもあったりするので、しょうがない部分はありますが、 宣言的UIを書くには関数の方が向いているなと改めて思いました。
Reactにあったこれがない、とかに関してはしゃぞーがないまだまだ勉強中な部分あるのでまた興味が湧けば書きます。