every Tech Blog

株式会社エブリーのTech Blogです。

Jetpack Compose のbeta版を触ってみた

f:id:nanakookada:20210318191929p:plain

はじめに

日本時間の2021年2月25日に Jetpack Compose のbeta版がリリースされました。APIも固まってきたようですので触ってみた範囲のうち、導入的なところをコードで示しつつ、感想を述べていきます。

使用環境

使用した環境は以下の通りです。他にもandroidx.activityなどにcomposeがありますが、いずれも2021年3月15日時点で最新のバージョンを使用しました。
バージョンはJetpackのLibraries(*1)から調べることができます。

  • Android Studio Arctic Fox | 2020.3.1 Canary 9
  • androidx.compose 1.0.0-beta02

最初につくるもの

トップレベルの関数に @Composable を指定することで、その関数内にてComposeを使用したレイアウトを組めます。合わせて @Preview を指定すればAndroid Studio上でプレビューもできます。
このプレビューは同時に複数表示可能なので、プレビュー用の関数を複数作成すればダークテーマ対応有り/無しの表示を同時に確認できます。

@Composable
fun MyScreen() {
    // ここでComposeを使用して表示を組む
}

@Preview
@Composable
fun PreviewMyScreen() {
    // MyScreenで組んだ表示がAndroid Studio上にプレビューされる
    MyScreen()
}

レイアウトたち

Box、Column、Rowがそれぞれ従来のFrameLayoutやinearLayoutに相当しています。

Box {
    // 重なる
    Text("hoge")
    Text("piyo")
}

Column {
    // 縦に並ぶ
    Text("hoge")
    Text("piyo")
}

Row {
    // 横に並ぶ
    Text("hoge")
    Text("piyo")
}

他に、gradleファイルに指定を追加することでCompose版のConstraintLayoutも使えますが、公式Document中のConstraintLayoutの補足(*2)を読むと無理して使わなくても良さそうです。

// テキスト2つを縦に並べる
ConstraintLayout {
    val (text1, text2) = createRefs()

    Text(
        "hoge",
        modifier = Modifier.constrainAs(text1) {
            linkTo(
                parent.start, parent.top, parent.end, text2.top,
                0.dp, 0.dp, 0.dp, 8.dp
            )
        }
    )
    Text(
        "piyo",
        modifier = Modifier.constrainAs(text2) {
            linkTo(
                parent.start, text1.bottom, parent.end, parent.bottom,
                0.dp, 0.dp, 0.dp, 0.dp
            )
        }
    )
}

表示のパーツたち

Text、Button、Image、Cardなど多くの表示が揃っています。Spacerなるものもあり、わかりやすくmarginを仕込めます。
ただ、RecyclerView(ListView)相当がLazyColumn(or LazyRow)という名称であったりと、一部は従来の名前から大きく変わっている点に注意が必要です。

val items = (0 until 100).map { "item $it" }

LazyColumn(
    // 項目の間隔を空ける
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    item {
        // リストの一番上に横スクロールのリストを入れる
        LazyRow(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(items) { Text(it) }
        }
    }

    items(items) {
        // 縦スクロールのリストの項目としてテキストとボタンを横に並べる
        Row {
            Text(it)
            Spacer(modifier = Modifier.width(8.dp))
            Button(onClick = {
                // ボタンクリック時の処理
            }) {
                Text("button")
            }
        }
    }
}

ものが多すぎるので使いたいものを公式Reference(*3)から頑張って探す必要があります。androidx.composeパッケージ関連を漁れば色々と見つかります。

表示の設定を変更する

これまでレイアウトのxmlで指定していたlayout_widthやpaddingなどは Modifier というobjectを通して設定します。 Modifierにサイズやpaddingを設定する拡張関数があり、ものによってはColumnなどのscope限定で使える拡張関数が存在していることもあります。

Box(
    // 縦横とも画面一杯に広げてpaddingを設ける
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp)
) {
    Text(
        "hoge",
        // 背景を赤色かつ角に丸みを与え、中央に配置する
        modifier = Modifier
            .background(Color.Red, shape = RoundedCornerShape(8.dp))
            .align(Alignment.Center)
    )
}

表示の操作として行えることはModifierの関数だけなのでわかりやすいです。

ガワを作る

Scaffold() でMaterial Designに則った画面を簡単に構築できます。各種AppBarやFABを設定できる口があるので、従って作るだけでそれらしい画面になります。

Scaffold(
    // 他にもbottomBarやfloatingActionButtonなどを設定できる口がある
    topBar = {
        TopAppBar(
            title = { Text("title") },
            actions = {
                IconButton(onClick = {
                    // メニュークリック時の処理
                }) {
                    Icon(
                        imageVector = Icons.Default.ImageSearch,
                        contentDescription = "search"
                    )
                }
            }
        )
    },
    content = {
        // ここで画面の表示を作る
        MyContentScreen()
     }
)

その他

viewModelを viewModel() で取得できたり、Navigationによる表示切り替えも行えるため、やりたいことは一通り行えそうであることが感じとれます。
また、これまでに作成した既存のViewは AndroidView なるものを使用することでComposeの世界に引き込んだりもできます。

他にCompose独自に覚えることとして、remember系の関数で値を保持したり、表示更新の契機としてStateを操作したりと従来にはなかった考え方を覚えて行く必要があります。
このあたりはReactのComponentで表示を作るときに近いものを感じました。

ハマったところ

Android StudioがCanaryであったり、Composeがbetaであるためか、いくつかハマったところがありました。

  • viewModel()を使うとプレビューが表示されない
    • viewModel()を使わずにViewModelの実体を渡すか、あるいはViewModelから取得した値だけをComposableな関数へ渡す
    • プレビューを使わず、エミュレータや実機で確認するだけなら問題なし
  • 自動importがよきに行われないものがあるため毎回手動でimportを書くことになるものがあった
    • viewModel()を使うための import androidx.lifecycle.viewmodel.compose.viewModel
    • var value by remember { mutableStateOf("") } などと by を使ってStateのvalueへのシンタックスシュガーを利用する場合の import androidx.compose.runtime.getValue / setValue
      • stackoverflowの回答(*4)に助けられました
      • (3/29追記) by xxx.observeAsState を使用した場合のgetValue(※7)や by remember を使用した場合のsetValueのimportなど、一部に対応されたようです
  • BottomSheetやSnackbarの使い方のベストプラクティスがわからない
    • Textなどと同じように作ることでとりあえず表示は行えるが、BottomSheetScaffoldやSnackbarHostなるものがあるため、よりよい使い方があると思われる

さいごに

今回の記事は公式Document(*5)を一通り読んでその中のおおよそを触ったものの一部です。Composeの情報は多いため覚えきることも紹介しきることも難しいですが、触ってみた範囲ではxmlで組むより簡単に表示を構築できる印象がありました。
対応されたAndroid Strudioとともにstable版になる日が楽しみです。

Jetpack Composeを使ったチャレンジとして Android Dev Challenge (*6) なるものも開催されているので、挑んでみるのも良いと思います。

参照

*1
JetpackのLibraries
https://developer.android.com/jetpack/androidx/explorer

*2
公式Document中のConstraintLayoutの補足
https://developer.android.com/jetpack/compose/layout#contraintlayout のNote部分

*3
公式Reference
https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary

*4
stackoverflowの回答
https://stackoverflow.com/questions/64951605/var-value-by-remember-mutablestateofdefault-produce-error-why

*5
公式Document
https://developer.android.com/jetpack/compose

*6
Android Dev Challenge
こちらは最終チャレンジのWeek 4
https://android-developers.googleblog.com/2021/03/android-dev-challenge-4.html

※7
Android Studio Arctic Fox Canary 12のFixes https://androidstudio.googleblog.com/2021/03/android-studio-arctic-fox-canary-12.html