Compose をビューベースのアプリに追加する

1. 始める前に

Jetpack Compose は、当初から View の相互運用性を考慮して設計されており、Compose と View システムはリソースを共有し、互いに連携しながら UI を表示することができます。この機能により、Compose を既存のビューベースのアプリに追加することができるようになっています。つまり、アプリ全体が完全に Compose ベースになるまで、Compose と View をコードベース内で共存させることができるのです。

この Codelab では、Juice Tracker アプリのビューベースのリストアイテムを Compose に変更します。残りの Juice Tracker のビューは、必要に応じて自分で変換できます。

ビューベースの UI を備えたアプリがある場合、その UI 全体を一度に書き換えたくはないでしょう。この Codelab では、ビューベースの UI の中の 1 つのビューを Compose 要素に変換します。

前提条件

  • ビューベースの UI に精通していること。
  • ビューベースの UI を使用してアプリを作成する方法に関する知識。
  • ラムダを含む Kotlin 構文の使用経験。
  • Jetpack Compose でアプリを作成する方法に関する知識。

学習内容

  • Android ビューで作成した既存の画面に Compose を追加する方法。
  • ビューベースのアプリに追加されたコンポーザブルをプレビューする方法。

作成するアプリの概要

  • Juice Tracker アプリでビューベースのリストアイテムを Compose に変換します。

2. スターター アプリの概要

この Codelab では、ビューを使用して Android アプリを作成するで取得した Juice Tracker アプリの解答コードをスターター コードとして使用します。スターター アプリはすでに、Room 永続ライブラリを使用してデータを保存できる状態になっています。ユーザーは、ジュースの名前、説明、色、評価など、ジュースの情報をアプリ データベースに追加できます。

詳細や評価とともにジュース アイテムが表示されたスマートフォンの画面

この Codelab では、ビューベースのリストアイテムを Compose に変換します。

ジュースの詳細を示すリストアイテム

この Codelab のスターター コードをダウンロードする

まず、スターター コードをダウンロードします。

または、GitHub リポジトリのクローンを作成してコードを入手することもできます。

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

コードは JuiceTracker GitHub リポジトリで確認できます。

3. Jetpack Compose ライブラリを追加する

前述のとおり、Compose と View は特定の画面で共存できます。一部の UI 要素を Compose に、残りを View システムに保持できます。たとえば、Compose はリストのみとし、画面の残りの部分は View システムに保持できます。

以下の手順で、Compose ライブラリを Juice Tracker アプリに追加します。

  1. Android Studio で Juice Tracker を開きます。
  2. アプリレベルの build.gradle.kts を開きます。
  3. buildFeatures ブロック内に compose = true フラグを追加します。
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

このフラグにより、Android Studio が Compose を扱えるようになります。前の Codelab ではこのステップを実施していません。これは、新しい Android Studio Compose テンプレート プロジェクトを作成するときに、このコードが Android Studio によって自動的に生成されるためです。

  1. buildFeatures で、composeOptions ブロックを追加します。
  2. ブロック内で、kotlinCompilerExtensionVersion"1.5.1" に設定して Kotlin コンパイラ バージョンを設定します。
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. dependencies セクションで、Compose の依存関係を追加します。Compose をビューベースのアプリに追加するには、次の依存関係が必要です。これらの依存関係は、Compose とアクティビティとの統合、Compose デザイン コンポーネント ライブラリの追加、Compose Jetpack テーマのサポート、IDE のサポートを改善するツールの提供に役立ちます。
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

ComposeView を追加する

ComposeView は、Jetpack Compose の UI コンテンツをホストできる Android View です。setContent を使用して、ビューにコンテンツのコンポーズ可能な関数を指定します。

  1. layout/list_item.xml を開き、[Split] タブでプレビューを表示します。

この Codelab を終了すると、このビューがコンポーザブルに置き換わります。

f85c6002df3265e0.png

  1. エラーを解決するには、JuiceListAdapter.kt ですべての場所から ListItemBinding を削除します。JuiceListViewHolder クラスで、binding.rootcomposeView に置き換えます。
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)

エラーを解決するために、すべての場所から ListItemBinding を削除する必要があります。

  1. onCreateViewHolder() フォルダで、return() 関数を次のコードと一致するように更新します。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. JuiceListViewHolder クラスで、すべての private 変数を削除し、bind() 関数からすべてのコードを削除します。JuiceListViewHolder クラスは次のコードのようになります。
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. この時点で、com.example.juicetracker.databinding.ListItemBindingandroid.view.LayoutInflater のインポートを削除できます。
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. ファイル layout/list_item.xml を削除します。
  2. [Delete] ダイアログで [OK] を選択します。

86dd7cba7e181e54.png

4. コンポーズ可能な関数を追加する

次に、リストアイテムを出力するコンポーザブルを作成します。コンポーザブルは、Juice と、リストアイテムの編集と削除を行うための 2 つのコールバック関数を受け取ります。

  1. JuiceListAdapter.kt で、JuiceListAdapter クラス定義の後に、ListItem() というコンポーズ可能な関数を作成します。
  2. ListItem() 関数が Juice オブジェクトと削除用のラムダ コールバックを受け入れるようにします。
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

作成するリストアイテムのプレビューを確認します。ジュース アイコン、ジュースの詳細、削除ボタンのアイコンがあります。これらのコンポーネントは後ほど実装します。

cf3b235dcb93e998.png

Juice アイコンのコンポーザブルを作成する

  1. JuiceListAdapter.kt で、ListItem() コンポーザブルの後に、colorModifier を受け取る JuiceIcon() という別のコンポーズ可能な関数を作成します。
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. 次のコードに示すように、JuiceIcon() 関数内に color の変数と内容説明の変数を追加します。
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

colorLabelMap 変数と selectedColor 変数を使用して、ユーザー選択に関連付けられたカラーリソースを取得します。

  1. 2 つのアイコン ic_juice_coloric_juice_clear を重ねて表示する Box レイアウトを追加します。ic_juice_color アイコンは色合いが含まれ、中央に揃えて表示されます。
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

コンポーザブルの実装には慣れているはずなので、実装方法については詳しく説明していません。

  1. JuiceIcon() をプレビューする関数を追加します。色を Yellow として渡します。
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

黄色のジュースのアイコンのプレビュー

ジュースの詳細のコンポーザブルを作成する

JuiceListAdapter.kt に、ジュースの詳細を表示する別のコンポーズ可能な関数を追加する必要があります。また、名前と説明用の 2 つの Text コンポーザブルと、評価インジケーターを表示するための列レイアウトも必要です。そのための手順は次のとおりです。

  1. 次のコードに示すように、Juice オブジェクトと Modifier を受け取るコンポーズ可能な関数 JuiceDetails()、およびジュース名用のテキスト コンポーザブルとジュースの説明用のコンポーザブルを追加します。
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. 未解決の参照エラーを解決するために、RatingDisplay() というコンポーズ可能な関数を作成します。

4018a1be2b3e7399.png

View システムには、次の評価バーを表示する RatingBar があります。Compose には評価バー コンポーザブルがないため、この要素をゼロから実装する必要があります。

  1. 評価に従って星を表示する RatingDisplay() 関数を定義します。このコンポーズ可能な関数は、評価に基づいて星の数を表示します。

4 つ星付きの評価バー

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

Compose で星形のドローアブルを作成するために、星形のベクター アセットを作成する必要があります。

  1. [Project] ペインで、[drawable] > [New] > [Vector Asset] を右クリックします。

e3b2bd6a495bc9.png

  1. [Asset Studio] ダイアログで星形アイコンを検索します。塗りつぶされた星形アイコンを選択します。

開始アイコンが選択された [アイコンを選択] ダイアログ

  1. 星の色値を 625B71 に変更します。

ベクター アセットと色を設定する Asset Studio ダイアログ

  1. [Next] > [Finish] をクリックします。
  2. res/drawable フォルダにドローアブルが表示されます。

Android Studio のプロジェクト ペインが res drawable フォルダを指している

  1. プレビュー コンポーザブルを追加して、JuiceDetails コンポーザブルをプレビューします。
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

ジュース名、ジュースの説明、星評価バーがある

削除ボタンのコンポーザブルを作成する

  1. JuiceListAdapter.kt で、ラムダ コールバック関数と Modifier を受け取る DeleteButton() という別のコンポーズ可能な関数を追加します。
  2. 次のコードに示すように、ラムダを onClick 引数に設定し、Icon() を渡します。
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. 削除ボタンをプレビューするプレビュー関数を追加します。
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Android Studio の [削除] アイコンのプレビュー

5. ListItem 関数を実装する

リストアイテムの表示に必要なコンポーザブルがすべて揃ったので、それらをレイアウト内に配置できるようになりました。前のステップで定義した ListItem() 関数に注意してください。

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

JuiceListAdapter.kt で、次の手順で ListItem() 関数を実装します。

  1. Mdc3Theme {} ラムダ内に Row レイアウトを追加します。
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. Row ラムダ内で、子要素として作成した 3 つのコンポーザブル JuiceIconJuiceDetailsDeleteButton を呼び出します。
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

Modifier.weight(1f)JuiceDetails() コンポーザブルに渡すと、重み付けされていない子要素を測定した後に残った水平方向のスペースをジュースの詳細が占有するようになります。

  1. onDelete(input) ラムダと上部に配置する修飾子をパラメータとして DeleteButton コンポーザブルに渡します。
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. ListItem コンポーザブルをプレビューするプレビュー関数を作成します。
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

ビートジュースのディテールを添えた Android Studio リストアイテムのプレビュー

  1. ListItem コンポーザブルをビューホルダーにバインドします。clickable() ラムダ関数内で onEdit(input) を呼び出して、リストアイテムがクリックされたときに編集ダイアログを開くようにします。

JuiceListViewHolder クラスの bind() 関数内で、コンポーザブルをホストする必要があります。setContent メソッドを使用して Compose UI コンテンツをホストできる Android View である ComposeView を使用します。

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. アプリを実行します。好きなジュースを追加します。Compose のリストアイテムが表示されます。

入力ダイアログにジュースの詳細が入力されたスマートフォンの画面. 1 つのジュースがリストに表示されたスマートフォンの画面

おめでとうございます。ビューベースのアプリで Compose 要素を使用する最初の Compose 相互運用アプリを作成しました。

6. 解答コードを取得する

この Codelab の完成したコードをダウンロードするには、以下の git コマンドを使用します。

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。

解答コードを確認する場合は、GitHub で表示します

7. 関連リンク

Android デベロッパー ドキュメント

Codelab [中級]