1. 始める前に
はじめに
この時点で、Compose を使用したアプリの作成に精通し、XML、ビュー、ビュー バインディング、Fragment を使ったアプリの作成についてある程度理解しているはずです。ビューでアプリを構築したことで、Compose のような宣言型 UI でアプリを構築することの便利さに気付いたでしょう。しかし、Compose ではなくビューを使用するほうが合理的な場合があります。この Codelab では、ビューの相互運用機能を使用して、ビュー コンポーネントをモダンな Compose アプリに追加する方法を学びます。
この Codelab の執筆時点では、作成する予定の UI コンポーネントは Compose ではまだ使用できません。これはビュー相互運用機能を活用する絶好の機会です。
前提条件:
- ビューで Android アプリを作成するの Codelab で Compose での Android の基礎コースワークを完了していること。
 
必要なもの
- Android Studio がインストールされた、インターネットに接続できるパソコン。
 - デバイスまたはエミュレータ。
 - Juice Tracker アプリのスターター コード。
 
作成するアプリの概要
この Codelab では、Compose UI に Spinner、RatingBar、AdView の 3 つのビューを統合して、Juice Tracker アプリの UI を完成させる必要があります。これらのコンポーネントを作成するには、ビュー相互運用機能(またはビュー相互運用)を使用します。ビュー相互運用機能を使用して、ビューをコンポーズ可能な関数でラップすることで、実際にアプリにビューを追加できます。
 
 
コードのチュートリアル
この Codelab では、ビューで Android アプリを作成する の Codelab と Compose をビューベースのアプリに追加するの Codelab と同じ JuiceTracker アプリを使用します。このバージョンとの違いは、提供されているスターター コードが Compose だけでできている点です。このアプリは現在、入力ダイアログ シートとリスト画面上部の広告バナーに、色と評価の入力がありません。
bottomsheet ディレクトリには、入力ダイアログに関連するすべての UI コンポーネントが含まれています。このパッケージには、作成時に色と評価の入力用の UI コンポーネントが含まれている必要があります。
homescreen には、ホーム画面でホストされている UI コンポーネントが含まれます。これには JuiceTracker リストが含まれます。最終的には、このパッケージには作成時に広告バナーが含まれるようにする必要があります。
ボトムシートやジュースリストなどのメインの UI コンポーネントは JuiceTrackerApp.kt ファイルでホストされています。
2. スターター コードを取得する
まず、スターター コードをダウンロードします。
または、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 compose-starter
- Android Studio で 
basic-android-kotlin-compose-training-juice-trackerフォルダを開きます。 - Android Studio で Juice Tracker アプリコードを開きます。
 
3. Gradle 構成
Play 開発者サービスの広告の依存関係をアプリの build.gradle.kts ファイルに追加します。
app/build.gradle.kts
android {
   ...
   dependencies {
      ...
      implementation("com.google.android.gms:play-services-ads:22.2.0")
   }
}
4. セットアップ
Android マニフェストの activity タグの上に次の値を追加して、テスト用の広告バナーを有効にします。
AndroidManifest.xml
...
<meta-data
   android:name="com.google.android.gms.ads.APPLICATION_ID"
   android:value="ca-app-pub-3940256099942544~3347511713" />
...
5. 入力ダイアログを完成させる
このセクションでは、カラースピナーと評価バーを作成し、入力ダイアログを完成させます。カラースピナーは色を選択するコンポーネントであり、評価バーではジュースの評価を選択できます。次のデザインを確認してください。


カラースピナーを作成する
Compose でスピナーを実装するには、Spinner クラスを使用する必要があります。Spinner はコンポーズ可能な関数ではなくビュー コンポーネントであるため、相互運用機能を使用して実装する必要があります。
bottomsheetディレクトリにColorSpinnerRow.ktという名前の新しいファイルを作成します。- ファイル内に 
SpinnerAdapterという新しいクラスを作成します。 SpinnerAdapterのコンストラクタで、Intパラメータを受け取るonColorChangeというコールバック パラメータを定義します。SpinnerAdapterは、Spinnerのコールバック関数を処理します。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
AdapterView.OnItemSelectedListenerインターフェースを実装します。
このインターフェースを実装すると、スピナーのクリック動作を定義できます。このアダプタは、後ほどコンポーズ可能な関数で設定します。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
AdapterView.OnItemSelectedListenerのメンバー関数であるonItemSelected()とonNothingSelected()を実装します。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        TODO("Not yet implemented")
    }
    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
- 色を選択するとアプリで UI の選択された値が更新されるように、
onItemSelected()関数を変更してonColorChange()コールバック関数を呼び出します。 
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }
    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
- 何も選択しないときは最初の色(赤)がデフォルトの色になるように、
onNothingSelected()関数を変更して色を0に設定します。 
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }
    override fun onNothingSelected(parent: AdapterView<*>?) {
        onColorChange(0)
    }
}
コールバック関数を通じてスピナーの動作を定義する SpinnerAdapter は作成済みです。ここで、スピナーの内容を作成し、それにデータを設定する必要があります。
ColorSpinnerRow.ktファイル内、かつSpinnerAdapterクラスの外部で、ColorSpinnerRowという新しいコンポーズ可能な関数を作成します。ColorSpinnerRow()のメソッド シグネチャに、スピナー位置用のIntパラメータ、Intパラメータを受け取るコールバック関数、修飾子を追加します。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
}
- この関数内で、
JuiceColor列挙型を使用してジュースの色文字列リソースの配列を作成します。この配列がスピナーに入力される内容となります。 
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
}
InputRow()コンポーズ可能関数を追加し、入力ラベル用の色文字列リソースと、Spinnerが表示される入力行を定義する修飾子を渡します。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
   }
}
次に、Spinner を作成します。Spinner は View クラスであるため、Compose のビュー相互運用 API を利用してコンポーズ可能な関数にラップする必要があります。これを実現するには、AndroidView コンポーズ可能関数を使用します。
- Compose で 
Spinnerを使用するために、InputRowラムダ本体で、AndroidView()コンポーズ可能関数を作成します。AndroidView()コンポーズ可能関数は、コンポーズ可能な関数内でビューの要素または階層を作成します。 
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
      AndroidView()
   }
}
AndroidView コンポーズ可能関数は、次の 3 つのパラメータを取ります。
factoryラムダ。ビューを作成する関数です。updateコールバック。factoryで作成されたビューがインフレートされるときに呼び出されます。- コンポーズ可能な関数の修飾子 
modifier。 

AndroidViewを実装するために、まず修飾子を渡し、画面の最大幅を占有させます。factoryパラメータにラムダを渡します。factoryラムダはパラメータとしてContextを受け取ります。Spinnerクラスを作成してコンテキストを渡します。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         modifier = Modifier.fillMaxWidth(),
         factory = { context ->
            Spinner(context)
         }
      )
   }
}
RecyclerView.Adapter が RecyclerView にデータを提供するのと同じように、ArrayAdapter は Spinner にデータを提供します。Spinner には、色の配列を保持するアダプタが必要です。
ArrayAdapterを使用してアダプタを設定します。ArrayAdapterには、コンテキスト、XML レイアウト、配列が必要です。レイアウトにsimple_spinner_dropdown_itemを渡します。このレイアウトは、Android のデフォルトとして提供されています。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         modifier = Modifier.fillMaxWidth(),
         factory = { context ->
             Spinner(context).apply {
                 adapter =
                     ArrayAdapter(
                         context,
                         android.R.layout.simple_spinner_dropdown_item,
                         juiceColorArray
                     )
             }
         }
      )
   }
}
factory コールバックは、内部で作成された View のインスタンスを返します。update は、factory コールバックから返されるのと同じ型のパラメータを受け取るコールバックです。このパラメータは、factory によってインフレートされる View のインスタンスです。この例では、factory 内で Spinner が作成されたため、その Spinner のインスタンスは update ラムダ本体でアクセスできます。
spinnerを渡すupdateコールバックを追加します。updateで提供されるコールバックを使用してsetSelection()メソッドを呼び出します。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      //...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}
- 前に作成した 
SpinnerAdapterを使用して、updateでonItemSelectedListener()コールバックを設定します。 
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         // ...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}
これでカラースピナー コンポーネントのコードが完成しました。
- 次のユーティリティ関数を追加して、
JuiceColorの列挙型インデックスを取得します。これは次のステップで使用します。 
private fun findColorIndex(color: String): Int {
   val juiceColor = JuiceColor.valueOf(color)
   return JuiceColor.values().indexOf(juiceColor)
}
EntryBottomSheet.ktファイルのSheetFormコンポーズ可能関数にColorSpinnerRowを実装します。カラースピナーは「説明」テキストの後、ボタンの上に配置します。
bottomsheet/EntryBottomSheet.kt
...
@Composable
fun SheetForm(
   juice: Juice,
   onUpdateJuice: (Juice) -> Unit,
   onCancel: () -> Unit,
   onSubmit: () -> Unit,
   modifier: Modifier = Modifier,
) {
   ...
   TextInputRow(
            inputLabel = stringResource(R.string.juice_description),
            fieldValue = juice.description,
            onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) },
            modifier = Modifier.fillMaxWidth()
        )
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
   ButtonRow(
            modifier = Modifier
                .align(Alignment.End)
                .padding(bottom = dimensionResource(R.dimen.padding_medium)),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}
評価入力を作成する
bottomsheetディレクトリにRatingInputRow.ktという新しいファイルを作成します。RatingInputRow.ktファイルで、RatingInputRow()という新しいコンポーズ可能な関数を作成します。- メソッド シグネチャで、評価用の 
Int、選択変更を処理するIntパラメータを含むコールバック、修飾子を渡します。 
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
- 次のサンプルコードに示すように、
ColorSpinnerRowと同様に、AndroidViewを含むコンポーズ可能な関数にInputRowを追加します。 
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = {},
            update = {}
        )
    }
}
factoryラムダ本体で、RatingBarクラスのインスタンスを作成します。これにより、このデザインに必要な評価バーのタイプを指定します。stepSizeを1fに設定して、評価が整数のみになるようにします。
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = {}
        )
    }
}
ビューがインフレートされると、評価が設定されます。前述の通り、factory は RatingBar のインスタンスを update コールバックに返します。
- コンポーズ可能な関数に渡される評価を使用して、
updateラムダ本体でRatingBarインスタンスに評価を設定します。 - 新しい評価を設定したら、
RatingBarコールバックを使用してonRatingChange()コールバック関数を呼び出し、UI で評価を更新します。 
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = { ratingBar ->
                ratingBar.rating = rating.toFloat()
                ratingBar.setOnRatingBarChangeListener { _, _, _ ->
                    onRatingChange(ratingBar.rating.toInt())
                }
            }
        )
    }
}
これで、評価入力のコンポーズ可能な関数が完成しました。
EntryBottomSheetでRatingInputRow()コンポーズ可能関数を使用します。カラースピナーの後、ボタンの上に配置します。
bottomsheet/EntryBottomSheet.kt
@Composable
fun SheetForm(
    juice: Juice,
    onUpdateJuice: (Juice) -> Unit,
    onCancel: () -> Unit,
    onSubmit: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        ...
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
        RatingInputRow(
            rating = juice.rating,
            onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
        )
        ButtonRow(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}
広告バナーを作成する
homescreenパッケージで、AdBanner.ktという新しいファイルを作成します。AdBanner.ktファイルで、AdBanner()という新しいコンポーズ可能な関数を作成します。
前に作成したコンポーズ可能な関数とは異なり、AdBanner には入力は必要ありません。そのため、InputRow コンポーズ可能関数でラップする必要はありません。ただし、AndroidView は必要です。
AdViewクラスを使用して、自作のバナーを作成してみましょう。広告サイズをAdSize.BANNER、広告ユニット ID を"ca-app-pub-3940256099942544/6300978111"に設定してください。AdViewがインフレートされたら、AdRequest Builderを使用して広告を読み込みます。
homescreen/AdBanner.kt
@Composable
fun AdBanner(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            AdView(context).apply {
                setAdSize(AdSize.BANNER)
                // Use test ad unit ID
                adUnitId = "ca-app-pub-3940256099942544/6300978111"
            }
        },
        update = { adView ->
            adView.loadAd(AdRequest.Builder().build())
        }
    )
}
JuiceTrackerAppで、JuiceTrackerListの前にAdBannerを配置します。JuiceTrackerListは 83 行目で宣言されています。
ui/JuiceTrackerApp.kt
...
AdBanner(
   Modifier
       .fillMaxWidth()
       .padding(
           top = dimensionResource(R.dimen.padding_medium),
           bottom = dimensionResource(R.dimen.padding_small)
       )
)
JuiceTrackerList(
    juices = trackerState,
    onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
    onUpdate = { juice ->
        juiceTrackerViewModel.updateCurrentJuice(juice)
        scope.launch {
            bottomSheetScaffoldState.bottomSheetState.expand()
        }
     },
)
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 compose-with-views
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
解答コードを確認する場合は、GitHub で表示します。
7. 関連リンク
8. 最後に
このコースはここまでですが、これは Android アプリの開発の始まりにすぎません。
このコースでは、ネイティブ Android アプリを作成するための最新の UI ツールキットである Jetpack Compose を使用してアプリを作成する方法を学習しました。このコースでは、リストを使ったアプリ(単一画面または複数画面)を作成し、それらの間を移動しました。インタラクティブなアプリを作成し、ユーザー入力への応答を実装して、UI を更新する方法を学びました。マテリアル デザインを適用し、色、シェイプ、タイポグラフィを使用してアプリにテーマを設定しました。また、Jetpack とその他のサードパーティ ライブラリを使用して、タスクのスケジュール設定、リモート サーバーからのデータの取得、ローカルでのデータの保持などを行いました。
このコースを完了することで、Jetpack Compose を使用して美しくレスポンシブなアプリを作成する方法をよく理解しただけでなく、効率的でメンテナンスしやすく、視覚的に魅力のある Android アプリを作成するために必要な知識とスキルを身に付けることができました。この基礎知識を身に付けておけば、最新の Android 開発や Compose のスキルを継続的に学習して身に付けることができます。
このコースにご参加いただき、ありがとうございました。今後も、Android デベロッパー向けドキュメント、Android デベロッパー向けの Jetpack Compose コース、最新の Android アプリ アーキテクチャ、Android デベロッパー ブログ、その他の Codelab、サンプル プロジェクトなどの資料も活用して、スキルを身に付け、その幅を広げることをおすすめします。
最後に、ソーシャル メディアで作成したコンテンツを共有し、その際にはハッシュタグ #AndroidBasics を付けて、Google と他の Android デベロッパー コミュニティがあなたの学習プロセスを追いかけることができるようにしましょう。
Compose を使ったコーディングをお楽しみください!