Chatwork Creator's Note

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

読者になる

Jetpack Composeでチャット一覧を作る

はじめに

はじめまして、入社して半年が経ちましたモバイルアプリケーション開発部のmatsubaです。
今回はAndroidのUIを構築するための最新のツールキットであるJetpack Composeの基本的な使い方と、 Jetpack Composeを使ったチャットアプリについて説明します。

Jetpack Composeとは

Jetpack ComposeはKotlinで記述できるUIツールキットで、少ないコードでUIとそれに紐づくデータバインドの処理を実装できます。

また、執筆時の2021年5月ではまだベータ版ですが、Google I/O 2021で正式版が7月にリリースされるアナウンスがあり、 これからはJetpack Composeを使ってUIを組むことが推奨されていくそうです。

基本的な使い方

以下は任意のテキストを表示させる方法です。

まず、実装したいレイアウト要素を@Composableアノテーションを用いて定義し、その中にCompose UIライブラリで定義される関数を用いてレイアウト作っていきます。

ここではText()関数を使い、引数にmessageを使うことで可変のテキストを表示します。
また、modifierやstyleといった属性を使うことでレイアウトを調整することができます。
最後にsetContentブロックを用いてCompose関数を画面に追加します。

TextViewの他にもConstraintLayoutやRecyclerViewといったUIコンポーネントの代替もあるので、従来通りにレイアウトは組めると思います。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting(message = "Hello Chatwork!")
        }
    }

    @Composable
    fun Greeting(message: String) {
        val modifier = Modifier.padding(4.dp)
        val style = TextStyle(fontSize = 20.sp)
        Text(text = message, modifier = modifier, style = style)
    }
}

ちょっと応用

以下はUIを更新する例です。

viewModelにLiveDataでmessageを定義してそのメッセージが更新されると、Greeting関数が再実行されるようにしています。
このようにCompose関数を使うことでUIを処理ごとに分割しやすくなり、UIの状態による変化も簡単にわかりやすく書けます。

ソースコードのあちこちでfindViewByIdをして、UIを更新していたら表示がおかしくなった・・・ということが減りそうです。

class MainActivity : AppCompatActivity() {

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

    @Composable
    fun GreetingScreen() {
        val viewModel: GreetingViewModel = viewModel()
        val message by viewModel.message.observeAsState()
        Greeting(message)
    }

    @Composable
    fun Greeting(message: String) {
        val modifier = Modifier.padding(4.dp)
        val style = TextStyle(fontSize = 20.sp)
        Text(text = message, modifier = modifier, style = style)
    }
}

チャット一覧を表示する

compose-samples/Jetchat at master · android/compose-samples · GitHub
次にGoogleで公開されているサンプルアプリのJetChatを用いて一部抜粋、簡略化しながらチャット画面を表示する方法を見ていこうかと思います。
個々のチャットのレイアウトをCompose関数で作り、さらにそれらをリスト表示する実装を説明します。

チャット1つ分のCompose関数

Row()を使って、ユーザー画像とメッセージテキストを横並びに配置しています。
特定の条件によりレイアウトの要素を変えることもここでできます。

@Composable
fun Message(
    onAuthorClick: () -> Unit,
    msg: Message,
    isUserMe: Boolean
) {
    // TODO: get image from msg.author
    val image = if (isUserMe) {
        imageResource(id = R.drawable.ali)
    } else {
        imageResource(id = R.drawable.someone_else)
    }
    // 要素を横並びさせるレイアウト
    Row {
        // ユーザー画像
        Image(
            modifier = Modifier
                    .clickable(onClick = onAuthorClick)
                    .preferredSize(42.dp)
                    .clip(CircleShape)
                    .align(Alignment.Top),
            bitmap = image,
            contentScale = ContentScale.Crop
        )
        // メッセージテキスト
        Text(
            text = msg.content,
            modifier = Modifier
                    .padding(end = 16.dp)
                    .weight(1f)
        )
    }
}

チャット一覧

リストを作るには従来のRecyclerViewの代替になるLazyColumn()を使います。
これによりRecyclerViewのように現在表示されているリストのViewだけを描画するといった最適化を行ってくれます。
引数のチャット一覧のリストを上で作成したチャット1つ分のComposeに与えてます。

アイテム一覧をループで回してViewを追加していくので、従来のRecyclerViewのadapterとレイアウトを作り、それぞれに処理を記述する実装方法より直感的に実装ができ、ソースコードもシンプルになっています。

@Composable
fun Messages(
    messages: List<Message>,
    navigateToProfile: (String) -> Unit,
    scrollState: LazyListState,
    modifier: Modifier = Modifier
) {
    Box(modifier = modifier) {
        // TODO: get name from saved content
        val authorMe = stringResource(id = R.string.author_me)
        // RecyclerView
        LazyColumn(
            reverseLayout = true,
            state = scrollState,
            modifier = Modifier.fillMaxSize()
        ) {
            //チャットの数だけMessageのComposeを追加する
            messages.forEach {
                item {
                    Message(
                        onAuthorClick = { name -> navigateToProfile(name) },
                        msg = it,
                        isUserMe = it.author == authorMe
                    )
                }
            }
        }
    }
}

おわりに

今回はJetpack Composeの基本とチャットアプリを参考にリストを作る方法を説明しました。
Jetpack Composeを使うことによりデータによりレイアウトの状態を変える実装がしやすくなることが分かりました。

また、既存のアプリにも組み込めるので静的なレイアウトはそのままに動的に変わるレイアウト部分をJetpack Composeに置き換えていってみるのもいいかもしれません。