1. はじめに
Compose と View システムは併用できます。
この Codelab では、Sunflower の植物詳細画面の一部を Compose に移行します。実際のアプリを Compose に移行してみるために、プロジェクトのコピーを作成しました。
この Codelab を終了すると、必要に応じて移行を続行し、Sunflower の残りの画面を変換できるようになります。
この Codelab の学習を進める際のサポートとして、次の Code-Along 動画をご覧ください。
学習内容
この Codelab では、以下について学びます。
- さまざまな移行方法
- アプリを段階的に Compose に移行する方法
- Android ビューを使用して作成した既存の画面に Compose を追加する方法
- Compose 内から Android View を使用する方法
- Compose で View システムからのテーマを使用する方法
- View システムと Compose コードで画面をテストする方法
前提条件
- ラムダを含む Kotlin 構文の使用経験。
- Compose の基本に関する知識。
必要なもの
2. 移行を計画する
Compose への移行は、ご自身やチーム次第です。Jetpack Compose を既存の Android アプリに統合するには、さまざまな方法があります。一般的な移行戦略は次の 2 つです。
- Compose で画面をすべて新しく開発する
- 既存の画面を基に、コンポーネントを段階的に移行する
新しい画面での Compose
アプリを新しいテクノロジーにリファクタリングする際の一般的なアプローチは、アプリのために構築する新機能でそれを採用することです。今回の場合、新しい画面が該当します。アプリの新しい UI 画面を構築する必要がある場合、アプリの残りの部分は View システムのままにして、UI 画面には Compose を使用することを検討してください。
この場合、移行した機能の両端で Compose 相互運用を行います。
Compose と View の併用
ある画面で、一部を Compose に移行し、その他の部分を View システムに移行することができます。たとえば、RecyclerView を移行し、画面の残りの部分を View システムに残すことができます。
またはその逆に、Compose を外部レイアウトとして使用し、Compose では使用できない既存のビュー(MapView や AdView など)を使用します。
完全移行
一度にすべてのフラグメントまたは画面を Compose に移行します。最も簡単ですが、あまり洗練された方法ではありません。
この Codelab で行うこと
この Codelab では、Compose と View を併用して、Sunflower の植物詳細画面を Compose に段階的に移行します。そうすることで、必要に応じて移行を続行できる十分な知識が身につきます。
3. 設定方法
コードを取得する
次のコマンドで、GitHub から Codelab のコードを取得します。
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。
Android Studio を開く
この Codelab には Android Studio Bumblebee が必要です。
サンプルアプリの実行
ダウンロードしたコードには、利用可能なすべての Compose Codelab のコードが含まれています。この Codelab を完了するには、Android Studio 内で MigrationCodelab
プロジェクトを開きます。
この Codelab では、Sunflower の植物詳細画面を Compose に移行します。植物のリスト画面に表示されている植物のいずれかをタップすると、植物詳細画面が開きます。
プロジェクトの設定
このプロジェクトには複数の git ブランチがあります。
main
は、チェックアウトまたはダウンロードしたブランチです。Codelab の出発点です。end
には、この Codelab の解答があります。
main
ブランチのコードから始め、マイペースで Codelab を進めることをおすすめします。
Codelab の途中には、プロジェクトに追加する必要があるコード スニペットを記載しています。場所によってはコードを削除する必要もありますが、この部分はコード スニペットのコメントに明示的に記載されています。
git を使用して end
ブランチを取得するには、次のコマンドを使用します。
$ git clone -b end https://github.com/googlecodelabs/android-compose-codelabs
または、次の場所から解答コードをダウンロードします。
よくある質問
4. Sunflower での Compose
Compose は、main
ブランチからダウンロードしたコードにすでに追加されています。しかし、動作させるために必要なものを確認しましょう。
app/build.gradle
(または build.gradle (Module: compose-migration.app)
)ファイルを開くと、Compose の依存関係がインポートされ、Android Studio で buildFeatures { compose true }
フラグを使用して Compose が扱えるようになる仕組みが確認できます。
app/build.gradle
android {
...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion rootProject.composeVersion
}
}
dependencies {
...
// Compose
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
implementation "androidx.compose.material:material:$rootProject.composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
これらの依存関係のバージョンは、ルート build.gradle
ファイルで定義されています。
5. Compose の使用を開始する
植物詳細画面で、画面の全体構造を維持したまま、植物の説明を Compose に移行します。ここでは、「移行を計画する」のセクションで説明した「Compose と View の併用」移行戦略に沿って進めます。
UI をレンダリングするために、Compose はホストのアクティビティまたはフラグメントを必要とします。Sunflower では、すべての画面がフラグメントを使用しているため、ComposeView
を使用します。これは、setContent
メソッドを使用して Compose UI コンテンツをホストできる Android View です。
XML コードの削除
移行を始めましょう。fragment_plant_detail.xml
を開き、次の操作を行います。
- コードビューに切り替えます。
NestedScrollView
内のConstraintLayout
コードとネストされたTextView
を削除します(この Codelab では、個々のアイテムを移行する際に XML コードを比較して参照するため、コードをコメントアウトすると便利です)。- 代わりに Compose コードをホストする
ComposeView
を追加し、compose_view
をビュー ID とします。
fragment_plant_detail.xml
<androidx.core.widget.NestedScrollView
android:id="@+id/plant_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_bottom_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
// Step 2) Comment out ConstraintLayout and its children
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
<TextView
android:id="@+id/plant_detail_name"
...
</androidx.constraintlayout.widget.ConstraintLayout>
// End Step 2) Comment out until here
// Step 3) Add a ComposeView to host Compose code
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
Compose コードの追加
これで、植物詳細画面の Compose への移行を開始する準備が整いました。
この Codelab では、plantdetail
フォルダの PlantDetailDescription.kt
ファイルに Compose コードを追加します。このファイルを開いて、"Hello Compose!"
というプレースホルダ テキストがプロジェクトですでに利用可能になっていることを確認します。
plantdetail/PlantDetailDescription.kt
@Composable
fun PlantDetailDescription() {
Text("Hello Compose")
}
前のステップで追加した ComposeView
からこのコンポーザブルを呼び出して、画面に表示しましょう。plantdetail/PlantDetailFragment.kt
を開きます。
画面はデータ バインディングを使用しているため、composeView
に直接アクセスして setContent
を呼び出すと、Compose コードを画面に表示できます。Sunflower はマテリアル デザインを使用しているため、MaterialTheme
内で PlantDetailDescription
コンポーザブルを呼び出します。
plantdetail/PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
// You're in Compose world!
MaterialTheme {
PlantDetailDescription()
}
}
}
...
}
}
アプリを実行すると、画面に「Hello Compose!
」と表示されます。
6. XML からのコンポーザブルの作成
まず、植物の名前を移行しましょう。正確には、fragment_plant_detail.xml
で削除した ID @+id/plant_detail_name
の TextView
です。XML コードは次のとおりです。
<TextView
android:id="@+id/plant_detail_name"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
android:textAppearance="?attr/textAppearanceHeadline5"
... />
スタイルは textAppearanceHeadline5
、横方向のマージンは 8.dp
であり、画面上で横方向に中央揃えされています。ただし、表示されるタイトルは、リポジトリ レイヤからの PlantDetailViewModel
で公開される LiveData
からモニタリングされます。
LiveData
のモニタリングについては後述します。名前が利用可能であり、PlantDetailDescription.kt
ファイルで作成した新しい PlantName
コンポーザブルにパラメータとして渡されたとしましょう。このコンポーザブルは、後で PlantDetailDescription
コンポーザブルから呼び出されます。
PlantDetailDescription.kt
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Preview
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
プレビュー:
ここで
Text
のスタイルはMaterialTheme.typography.h5
であり、XML コードのtextAppearanceHeadline5
にマッピングされています。- 修飾子がテキストを装飾し、XML 版のように調整します。
fillMaxWidth
修飾子は XML コードのandroid:layout_width="match_parent"
に対応します。- 横方向の
padding
はmargin_small
であり、この値はdimensionResource
ヘルパー関数を使用して View システムから取得します。 wrapContentWidth
はText
を横方向に揃えます。
7. ViewModel と LiveData
それでは、タイトルを画面につなぎましょう。そのためには、PlantDetailViewModel
を使用してデータを読み込む必要があります。そこで、Compose には ViewModel と LiveData の統合が用意されています。
ViewModel
Fragment で PlantDetailViewModel
のインスタンスが使用されるため、これをパラメータとして PlantDetailDescription
に渡すだけで済みます。
PlantDetailDescription.kt
ファイルを開き、PlantDetailViewModel
パラメータを PlantDetailDescription
に追加します。
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
このコンポーザブルをフラグメントから呼び出すときに、ViewModel のインスタンスを渡します。
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
LiveData
これで、PlantDetailViewModel
の LiveData<Plant>
フィールドにアクセスし、植物名を取得できるようになりました。
コンポーザブルから LiveData をモニタリングするには、LiveData.observeAsState()
関数を使用します。
LiveData が出力する値は null になる可能性があるため、その使用を null チェックでラップする必要があります。そのため、再利用性を考えて、LiveData の使用とリッスンを異なるコンポーザブルに分割することをおすすめします。したがって、PlantDetailContent
という新しいコンポーザブルを作成して Plant
情報を表示します。
以上により、LiveData モニタリングを追加した PlantDetailDescription.kt
ファイルは次のようになります。
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
// Observes values coming from the VM's LiveData<Plant> field
val plant by plantDetailViewModel.plant.observeAsState()
// If plant is not null, display the content
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
現時点で PlantDetailContent
は PlantName
を呼び出すだけであるため、プレビューは PlantNamePreview
と同じです。
これで、ViewModel が Compose につながり、Compose で植物名が表示されるようになりました。以降のセクションでは、残りのコンポーザブルを作成して、同様の方法で ViewModel につなぎます。
8. 他の XML コードの移行
UI にない情報(水やり情報、植物の説明など)を簡単に記入できるようになりました。前述の XML コードと同様のアプローチで、画面の残りの部分を移行できます。
以前に fragment_plant_detail.xml
から削除した水やり情報の XML コードは、ID が plant_watering_header
と plant_watering
の 2 つの TextView で構成されています。
<TextView
android:id="@+id/plant_watering_header"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@string/watering_needs_prefix"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
... />
<TextView
android:id="@+id/plant_watering"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
app:wateringText="@{viewModel.plant.wateringInterval}"
.../>
以前と同様に、PlantWatering
という新しいコンポーザブルを作成し、Text
を追加して、水やり情報を画面に表示します。
PlantDetailDescription.kt
@Composable
private fun PlantWatering(wateringInterval: Int) {
Column(Modifier.fillMaxWidth()) {
// Same modifier used by both Texts
val centerWithPaddingModifier = Modifier
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.align(Alignment.CenterHorizontally)
val normalPadding = dimensionResource(R.dimen.margin_normal)
Text(
text = stringResource(R.string.watering_needs_prefix),
color = MaterialTheme.colors.primaryVariant,
fontWeight = FontWeight.Bold,
modifier = centerWithPaddingModifier.padding(top = normalPadding)
)
val wateringIntervalText = LocalContext.current.resources.getQuantityString(
R.plurals.watering_needs_suffix, wateringInterval, wateringInterval
)
Text(
text = wateringIntervalText,
modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
)
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MaterialTheme {
PlantWatering(7)
}
}
プレビュー:
注意点:
- 横方向のパディングと配置の装飾は
Text
コンポーザブルで共有されるため、ローカル変数(centerWithPaddingModifier
)に代入することで Modifier を再利用できます。修飾子は標準の Kotlin オブジェクトであるため、このようにできます。 - Compose の
MaterialTheme
は、plant_watering_header
で使用されているcolorAccent
と完全一致しません。ひとまず、テーマ設定のセクションで改善するMaterialTheme.colors.primaryVariant
を使用しましょう。
すべての要素をつなげて、PlantDetailContent
からも PlantWatering
を呼び出しましょう。最初に削除した ConstraintLayout XML コードではマージンが 16.dp
でした。これを Compose コードに含める必要があります。
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
PlantDetailContent
で、Column
を作成して名前と水やり情報をまとめて表示し、パディングとします。また、使用する背景色とテキスト色が適切になるように、その処理を行う Surface
を追加します。
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
}
}
}
プレビューを更新すると、次のように表示されます。
9. Compose コードでの View
植物の説明文を移行しましょう。fragment_plant_detail.xml
のコードでは、TextView
に app:renderHtml="@{viewModel.plant.description}"
を指定して、画面に表示されるテキストを XML に伝えていました。renderHtml
は、PlantDetailBindingAdapters.kt
ファイルにあるバインディング アダプターです。この実装では、HtmlCompat.fromHtml
を使用して TextView
にテキストを設定しています。
しかし、Compose は現在のところ Spanned
クラスや HTML 形式のテキストの表示をサポートしていません。そのため、この制限を回避するために Compose コードで View システムの TextView
を使用する必要があります。
Compose ではまだ HTML コードをレンダリングできないため、AndroidView
API を使用してプログラムで TextView
を作成します。
AndroidView
は View
をパラメータとして受け取り、View がインフレートされたときのコールバックを提供します。
そのために、新しい PlantDescription
コンポーザブルを作成しましょう。このコンポーザブルは、ラムダで記憶した TextView
を使用して AndroidView
を呼び出します。factory
コールバックで、所定の Context
を使用して HTML インタラクションに応答する TextView
を初期化します。update
コールバックで、記憶した HTML 形式の説明を使用してテキストを設定します。
PlantDetailDescription.kt
@Composable
private fun PlantDescription(description: String) {
// Remembers the HTML formatted description. Re-executes on a new description
val htmlDescription = remember(description) {
HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
// Displays the TextView on the screen and updates with the HTML description when inflated
// Updates to htmlDescription will make AndroidView recompose and update the text
AndroidView(
factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = htmlDescription
}
)
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MaterialTheme {
PlantDescription("HTML<br><br>description")
}
}
プレビュー:
htmlDescription
は、パラメータとして渡された description
について、HTML 形式の説明を記憶しています。description
パラメータが変更された場合、remember
内の htmlDescription
コードが再度実行されます。
同様に、htmlDescription
が変更された場合、AndroidView
更新コールバックが再コンポーズされます。コールバック内で状態が読み取られると、再コンポーズが発生します。
PlantDescription
を PlantDetailContent
コンポーザブルに追加し、HTML の説明も表示するようにプレビュー コードを変更しましょう。
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
PlantDescription(plant.description)
}
}
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
プレビュー:
この時点で、元の ConstraintLayout
内のコンテンツはすべて Compose に移行しました。アプリを実行して、想定どおりに動作することを確認します。
10. ViewCompositionStrategy
デフォルトでは、ComposeView
がウィンドウからデタッチされるたびに、Compose は Composition を破棄します。複数の理由によりフラグメントで ComposeView
が使用されている場合、これは望ましくありません。
- Composition は、Compose UI の
View
タイプに対するフラグメントのビュー ライフサイクルに従って、状態を保存する必要があります。 - 遷移またはウィンドウ遷移が発生したときに、Compose UI 要素を画面上に維持します。遷移中、
ComposeView
自体はウィンドウからデタッチされた後も引き続き表示されます。
AbstractComposeView.disposeComposition
メソッドを手動で呼び出して、Composition を手動で破棄できます。あるいは、Composition が不要になったときに自動的に破棄するには、別の戦略を設定するか、setViewCompositionStrategy
メソッドを呼び出して独自の戦略を作成します。
DisposeOnViewTreeLifecycleDestroyed
戦略を使用して、フラグメントの LifecycleOwner
が破棄されたときに Composition を破棄します。
PlantDetailFragment
には遷移の出入りがあるため(詳細は nav_garden.xml
をご覧ください、後ほど Compose 内で View
タイプを使用し、また、ComposeView
が DisposeOnViewTreeLifecycleDestroyed
戦略を使用するようにする必要があります。ただし、フラグメントで ComposeView
を使用する場合は、常にこの戦略を設定することをおすすめします。
plantdetail/PlantDetailFragment.kt
import androidx.compose.ui.platform.ViewCompositionStrategy
...
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
// Dispose the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
...
}
}
11. 相互運用性のテーマ設定
植物の詳細のテキスト コンテンツを Compose に移行しました。しかし、Compose では適切なテーマカラーが使用されていません。緑色を使用すべき植物名に、紫色を使用しています。
この移行の初期段階では、Compose で独自のマテリアル テーマを最初から書き換えるのではなく、View システムで利用可能なテーマを継承することをおすすめします。マテリアル テーマは、Compose に付属するすべてのマテリアル デザイン コンポーネントに対応しています。
View システムのマテリアル デザイン コンポーネント(MDC)テーマを Compose で再利用するには、compose-theme-adapter を使用します。MdcTheme
関数は、ホスト コンテキストの MDC テーマを自動的に読み取り、ライトモードとダークモードの両方でデベロッパーに代わって MaterialTheme
に渡します。この Codelab ではテーマカラーだけでなく、View システムのシェイプとタイポグラフィも読み取ります。
このライブラリはすでに、app/build.gradle
ファイルに次のように含まれています。
...
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
これを使用するには MaterialTheme
の使用を MdcTheme
に置き換えます。たとえば、PlantDetailFragment
では次のようになります。
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
composeView.apply {
...
setContent {
MdcTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
PlantDetailDescription.kt
ファイル内のすべてのプレビュー コンポーザブルは、次のようになります。
PlantDetailDescription.kt
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
@Preview
@Composable
private fun PlantNamePreview() {
MdcTheme {
PlantName("Apple")
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MdcTheme {
PlantWatering(7)
}
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MdcTheme {
PlantDescription("HTML<br><br>description")
}
}
プレビューからわかるように、MdcTheme
は styles.xml
ファイルのテーマから色を選択します。
新しい関数を作成し、プレビューの uiMode
に Configuration.UI_MODE_NIGHT_YES
を渡すことで、UI をダークモードでプレビューすることもできます。
import android.content.res.Configuration
...
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlantDetailContentDarkPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
プレビュー:
アプリを実行すると、ライトモードとダークモードの両方で、移行前とまったく同じように動作します。
12. テスト
植物詳細画面の一部を Compose に移行した後は、何も問題がないことを確認するためのテストが不可欠です。
Sunflower では、androidTest
フォルダにある PlantDetailFragmentTest
によってアプリの一部の機能がテストされます。このファイルを開き、現在のコードを確認します。
testPlantName
は、画面上の植物の名前を確認しますtestShareTextIntent
は、共有ボタンをタップした後に正しいインテントがトリガーされることを確認します
アクティビティやフラグメントで Compose を使用する場合、ActivityScenarioRule
を使用するのではなく、ActivityScenarioRule
を ComposeTestRule
と統合する createAndroidComposeRule
を使用する必要があります。これにより、Compose コードをテストできるようになります。
PlantDetailFragmentTest
で、ActivityScenarioRule
の使用を createAndroidComposeRule
に置き換えます。テストを設定するためにアクティビティ ルールが必要な場合は、次のように createAndroidComposeRule
の activityRule
属性を使用します。
@RunWith(AndroidJUnit4::class)
class PlantDetailFragmentTest {
@Rule
@JvmField
val composeTestRule = createAndroidComposeRule<GardenActivity>()
...
@Before
fun jumpToPlantDetailFragment() {
populateDatabase()
composeTestRule.activityRule.scenario.onActivity { gardenActivity ->
activity = gardenActivity
val bundle = Bundle().apply { putString("plantId", "malus-pumila") }
findNavController(activity, R.id.nav_host).navigate(R.id.plant_detail_fragment, bundle)
}
}
...
}
テストを実行すると、testPlantName
が失敗します。testPlantName
は、TextView が画面上にあるかどうかを確認します。しかし、UI のその部分は Compose に移行済みです。そのため、代わりに Compose のアサーションを使用する必要があります。
@Test
fun testPlantName() {
composeTestRule.onNodeWithText("Apple").assertIsDisplayed()
}
テストを実行すると、すべて合格します。
13. 完了
これで、この Codelab は終了です。
元の Sunflower github プロジェクトの compose
ブランチは、植物詳細画面を Compose に完全に移行しています。この Codelab で行ったこととは別に、CollapsingToolbarLayout の動作もシミュレートします。これには以下が含まれます。
- Compose による画像の読み込み
- アニメーション
- ディメンション処理の向上
- その他
次のステップ
Compose パスウェイに関する他の Codelab をご確認ください。
参考資料
- Jetpack Compose の移行についての Code-Along 動画
- 既存のアプリでの Compose に関するガイド
- Crane(サンプルアプリ)。Compose に MapView を埋め込んでいます