every Tech Blog

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

Jetpack Composeのチュートリアルをやってみた

f:id:nanakookada:20211126110305p:plain

はじめに

こんにちは。MAMADAYSでAndroidアプリの開発を担当している高野です。

UIはXMLで作成したほうが楽なのではないかと思い、まだ Jetpack Composeを触ったことがなかったのでチュートリアルに沿って進めながら体験してみたいと思います。

※Jetpack Compose は Android の UI を構築するための新しいツールキットです。2021年7月にバージョン 1.0 をリリースし、チュートリアルのページも用意されました。

チュートリアルでやること

チュートリアルでは4つのレッスンを通して次のことを学ぶことができるようです。

  1. 要素の追加
  2. プレビュー方法
  3. 複数要素のレイアウト
  4. デザインテーマの適用
  5. リストの実装
  6. アニメーションの実装

環境の準備

現在使っているAndroid Studioをそのまま使用します。

  • Android Studio Arctic Fox 2020.3.1 Patch 3

プロジェクトは新規で作成し、テンプレートは Empty Activity を使用します。

f:id:itsmynote:20211124185126p:plain:w380

Empty Activity のプロジェクトでJetpack Composeを使用できるようにセットアップの例に合わせてgradleファイルを修正します。

build.gradle

@@ -6,7 +6,7 @@ buildscript {
     }
     dependencies {
         classpath "com.android.tools.build:gradle:7.0.3"
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21"

         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files

app/build.gradle

@@ -15,7 +15,9 @@ android {

         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
-
+    buildFeatures {
+        compose true
+    }
     buildTypes {
         release {
             minifyEnabled false
@@ -28,6 +30,11 @@ android {
     }
     kotlinOptions {
         jvmTarget = '1.8'
+        useIR = true
+    }
+    composeOptions {
+        kotlinCompilerVersion '1.4.21'
+        kotlinCompilerExtensionVersion '1.0.0-alpha10'
     }
 }

@@ -37,7 +44,17 @@ dependencies {
     implementation 'androidx.appcompat:appcompat:1.3.1'
     implementation 'com.google.android.material:material:1.4.0'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+
+    implementation 'androidx.compose.ui:ui:1.0.0-alpha10'
+    implementation 'androidx.compose.ui:ui-tooling:1.0.0-alpha10'
+    implementation 'androidx.compose.foundation:foundation:1.0.0-alpha10'
+    implementation 'androidx.compose.material:material:1.0.0-alpha10'
+    implementation 'androidx.compose.material:material-icons-core:1.0.0-alpha10'
+    implementation 'androidx.compose.material:material-icons-extended:1.0.0-alpha10'
+    implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-alpha10'
+
     testImplementation 'junit:junit:4.+'
     androidTestImplementation 'androidx.test.ext:junit:1.1.3'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.0.0-alpha10'
 }

この後の流れはチュートリアルの内容をなぞるだけなので実装については割愛します。

チュートリアルを終えて

チュートリアルはとてもよくできていて、つまずくポイントはほとんどありませんでした。強いて言えば、Lesson3のマテリアルデザインを使用するためのComposeTutorialThemeって何だろう…?という部分ぐらいです。一通り終えるため、今回はThemeと入力した時にサジェストされた MaterialThemeという記述で代用しました。

Lesson1からLesson4までを通して次のような成果物ができました。

f:id:itsmynote:20211124185131g:plain

少しカスタマイズしてみる

チュートリアルのサンプルはチャット画面のようなレイアウトになっています。サンプルのままだと一人でチャットしているようで寂しいので、登場人物を二人にしてみたいと思います。

イメージとしてはこんな感じです。

f:id:itsmynote:20211124185205p:plain:w380

Message の修正

データクラスMessageをsealed classにしてMe/Youを追加します。

sealed class Message {
    abstract val author: String
    abstract val body: String

    data class Me(override val author: String, override val body: String) : Message()
    data class You(override val author: String, override val body: String) : Message()
}

サンプルデータ

サンプルデータは好みで修正します。

object SampleData {
    // Sample conversation data
    val conversationSample = listOf(
        Message.You(
            "Colleague",
            "Test...Test...Test..."
        ),
        Message.Me(
            "Me",
            "List of Android versions:\n" +
...

幅一杯に広げる

ModifierクラスにfillMaxWidthという関数があるので追加します。

@Composable
fun MessageCard(msg: Message) {
    Row(
        modifier = Modifier
            .padding(all = 8.dp)
            .fillMaxWidth()

位置の調整

MessageCardを含むRowの定義を見てみます。

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
) {
        ...

horizontalArrangementという引数があります。この引数にArrangementの値を渡すことで位置の調整ができるようです。horizontalArrangementを指定する際に引数msgの型を判定して位置を調整します。

@Composable
fun MessageCard(msg: Message) {
    Row(
        modifier = Modifier
            .padding(all = 8.dp)
            .fillMaxWidth(),
        horizontalArrangement = when (msg) {
            is Message.Me -> Arrangement.End
            is Message.You -> Arrangement.Start
        }

合わせて、アイコンや名前・メッセージも修正を加えます。名前とメッセージはColumnの中なので、Columnの定義をみてみます。

inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
        ...

Columnの場合はhorizontalAlignmentAlignmentを指定するようですね。ついでにsurfaceColorも修正しています。

if (msg is Message.You) {
    Image(
        painter = painterResource(R.drawable.ic_launcher_foreground),
        modifier = Modifier
            .size(40.dp)
            .clip(CircleShape)
            .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
    )

    Spacer(modifier = Modifier.width(8.dp))
}

...
val surfaceColor: Color by animateAsState(
    if (isExpanded) MaterialTheme.colors.primary else when (msg) {
        is Message.Me -> MaterialTheme.colors.surface
        is Message.You -> MaterialTheme.colors.secondary
    },
)

Column(
    modifier = Modifier.clickable { isExpanded = !isExpanded },
    horizontalAlignment = when (msg) {
        is Message.Me -> Alignment.End
        is Message.You -> Alignment.Start
    }
) {
        ...
}

if (msg is Message.Me) {
    Spacer(modifier = Modifier.width(8.dp))

    Image(
        painter = painterResource(R.drawable.ic_launcher_foreground),
        modifier = Modifier
            .size(40.dp)
            .clip(CircleShape)
            .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
    )
}

エミュレータで確認

チャット画面らしくなりましたね。

f:id:itsmynote:20211124185207p:plain:w380

最後に

Composeでの実装は想像していたよりずっと簡単でした。特に、リストがたった5行で実装できることに驚きました。RecyclerViewと比べるととても簡単ですよね。

まだComposeに慣れていないためもどかしさは感じましたが、簡単なアプリをいくつか作っていくうちに感覚的につかめそうだなといった印象です。