1. 簡介
Compose 和 View 系統可以同時搭配使用。
在本程式碼研究室中,您會將 Sunflower 植物詳細資料畫面的一部分內容遷移至 Compose。我們建立了一份專案複本,協助您嘗試將實際的應用程式遷移至 Compose。
完成本程式碼研究室後,您可以繼續進行遷移作業,並視需要轉換 Sunflower 的其餘畫面。
如果您在閱讀本程式碼研究室時需要更多支援,請參閱下列程式碼:
學習目標
在本程式碼研究室,您將學到:
- 可採用的不同遷移路徑
- 如何將應用程式逐步遷移至 Compose
- 如何將 Compose 新增至使用 Android View 所建立的現有畫面
- 如何在 Compose 中使用 Android View
- 如何在 Compose 中使用 View 系統中的主題
- 如何使用 View 系統和 Compose 程式碼來測試畫面
必要條件
- 熟悉 Kotlin 語法,包括 lambda
- 瞭解 Compose 基本概念
您需要準備的項目
2. 規劃遷移作業
遷移至 Compose 的方式取決於您和您的團隊。將 Jetpack Compose 整合到現有 Android 應用程式中的方法有很多種,兩個常見的遷移策略為:
- 完全使用 Compose 開發新的畫面
- 使用現有畫面,並逐步遷移其元件。
新畫面中的 Compose
將您的應用程式重構為新技術的常見方法,就是在為應用程式開發的新功能中採用這種技術。如此一來,就可以套用新的畫面。如果您需要為應用程式建立新的 UI 畫面,不妨使用 Compose,並且讓其他應用程式繼續保留在 View 系統中。
在這種情況下,您將會在這些已遷移功能的邊緣落實 Compose 互通性。
同時使用 Compose 及 View
針對畫面,您可以將部分元件遷移到 Compose,其他部分則留在 View 系統中。舉例來說,您可以遷移 RecyclerView,同時將畫面其他部分保留在 View 系統。
您也可以使用 Compose 做為外部版面配置,並使用 Compose 中可能無法使用的某些現有檢視,例如 MapView 或 AdView。
完成遷移
一次將整個片段或畫面移轉至 Compose。這個方法最簡單,但也最粗糙。
本程式碼研究室的目標?
在本程式碼研究室中,您會將 Sunflower 植物詳細畫面逐步遷移至 Compose,讓 Compose 和 View 互相搭配運作。完成後,您就可以視需要繼續進行遷移作業。
3. 開始設定
取得程式碼
從 GitHub 取得程式碼研究室程式碼:
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
或者,您也可以將存放區下載為 ZIP 檔案:
開啟 Android Studio
本程式碼研究室需要 Android Studio Bumblebee。
執行範例應用程式
您剛才下載的程式碼包含所有 Compose 程式碼研究室可用的程式碼。如要完成本程式碼研究室,請在 Android Studio 中開啟 MigrationCodelab
專案。
在這個程式碼研究室中,您會將「向日葵」植物詳細資料畫面遷移到 Compose。輕觸植物詳細資料畫面上的任一植物,即可開啟植物詳細資訊畫面。
專案設定
專案在多個 Git 分支版本中建構而成:
main
是您查看或下載的分支版本。這是程式碼研究室的起點。end
包含本程式碼研究室的解決方案。
建議您先從 main
分支版本的程式碼著手,依自己的步調逐步完成本程式碼研究室。
在本程式碼研究室的學習過程中,我們會為您提供要新增到專案的程式碼片段。在某些地方,您還需要移除程式碼片段註解中明確提及的程式碼。
如要使用 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 依附元件,並使用 buildFeatures { compose true }
標記讓 Android Studio 能與 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. Hello Compose!
在植物詳細資訊畫面中,我們會把植物的說明遷移至 Compose,同時完整保留畫面的整體結構。本文將遵循「規劃您的遷移」一節中說明的「同時使用 Compose 及 View」遷移策略。
Compose 需要主機 Activity (活動) 或 Fragment (片段),才能轉譯 UI。在 Sunflower 中,由於所有畫面都是使用片段,因此您會使用 ComposeView
,也就是能透過其 setContent
方法代管 Compose UI 內容的 Android View。
移除 XML 程式碼
讓我們開始遷移作業!開啟 fragment_plant_detail.xml
,然後遵循下列步驟:
- 切換至程式碼檢視模式
- 移除
NestedScrollView
中的ConstraintLayout
程式碼和巢狀TextView
(程式碼研究室在遷移個別項目時會比較並參照 XML 程式碼,為程式碼加上註解會很實用) - 新增可代管 Compose 程式碼的
ComposeView
做為資料檢視 ID,而不使用compose_view
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!
在本程式碼研究室的整個流程中,您都會將程式碼新增至 plantdetail
資料夾的的 PlantDetailDescription.kt
檔案。請開啟該檔案,看看我們如何在專案中提供預留位置的 "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"
。margin_small
的水平padding
是來自 View 系統的值,使用dimensionResource
輔助函式。wrapContentWidth
會水平對齊Text
。
7. ViewModels 和 LiveData
現在,讓我們將標題連接至畫面。如要這麼做,您需要使用 PlantDetailViewModel
載入資料。為此,Compose 整合了 ViewModel 和 LiveData。
ViewModels
由於 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
相同:
現在,您已經完成在 Compose 中顯示植物名稱所需的 ViewModel 連接。在接下來幾個小節中,您將要建立其他可組成函式,並以類似的方式將這些函式連接至 ViewModel。
8. 其他 XML 程式碼遷移作業
現在,您可以更輕鬆地完成 UI 中遺漏的內容:澆灌資訊和植物說明。您只要按照先前介紹的 XML 程式碼方法,就可以遷移畫面的其餘部分。
您從 fragment_plant_detail.xml
移除的飲水資訊 XML 程式碼是由兩個 ID 為 plant_watering_header
和 plant_watering
的 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
) 以重複使用該修飾符。由於修飾符是一般的 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
中的程式碼包含具有 app:renderHtml="@{viewModel.plant.description}"
的 TextView
,可讓 XML 知道要在畫面上顯示哪些文字。renderHtml
是一個位於 PlantDetailBindingAdapters.kt
檔案中的繫結轉接器。實作程序會使用 HtmlCompat.fromHtml
來設定 TextView
上的文字!
不過,Compose 目前不支援 Spanned
類別或顯示 HTML 格式的文字。因此,我們需要在 Compose 程式碼中使用 View 系統中的 TextView
,才能略過這項限制。
由於 Compose 尚不支援轉譯 HTML 程式碼,因此您可以使用 AndroidView
API 以程式輔助的方式建立 TextView
。
AndroidView
會使用 View
做為參數,並在 View 已加載時向您傳送回呼。
讓我們來建立一個全新的 PlantDescription
可組合函式。這個可組合函式會使用我們剛才在 lambda 中記住的 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
方法來建立自己的策略。
當片段的 LifecycleOwner
遭到刪除時,使用 DisposeOnViewTreeLifecycleDestroyed
策略來處置 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 提供的質感設計元件完美搭配運作。
如要在 Compose 中重複使用 View 系統質感設計元件 (MDC) 主題,您可以使用 compose-theme-adapter。MdcTheme
函式會自動讀取主機內容的 MDC 主題、為您傳送至 MaterialTheme
,以用於淺色和深色主題。即使您只需要此程式碼研究室的主題色彩,這個程式庫也會讀取 View 系統的形狀和字體排版。
此程式庫「已納入」app/build.gradle
檔案中,如下所示:
...
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
如要使用此程式庫,請將 MdcTheme
的用法替換為 MaterialTheme
。例如,在 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
檔案中擷取主題的色彩。
您也可以預覽深色主題的 UI,方法是建立新函式並將 Configuration.UI_MODE_NIGHT_YES
傳送至預覽的 uiMode
:
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
) 時,就必須使用 createAndroidComposeRule
以整合 ActivityScenarioRule
與 ComposeTestRule
,藉此測試 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. 恭喜
恭喜,您已經成功完成本程式碼研究室!
原始 Sunflower GitHub 專案的 compose
分支版本可將植物的詳細資料畫面完全遷移至 Compose。除了您在本程式碼研究室中完成的工作之外,此版本也會模擬 CollapsingToolbarLayout 的行為。這些行為包括:
- 使用 Compose 載入圖片
- 動畫
- 更完善的維度處理方式
- 還有更多功能!
後續步驟
請參閱 Compose 課程中的其他程式碼研究室:
其他資訊
- Jetpack Compose 遷移學習程式碼
- 現有應用程式中的 Compose 指南
- 在 Compose 中嵌入 MapView 的範例應用程式 Crane