1. Pengantar
Pada codelab sebelumnya, Anda mulai mengubah aplikasi Reply agar adaptif dengan menggunakan class ukuran jendela dan menerapkan navigasi dinamis. Fitur ini merupakan dasar yang penting dan langkah pertama dalam membangun aplikasi untuk semua ukuran layar. Jika melewatkan codelab Membangun aplikasi adaptif dengan navigasi dinamis, Anda sangat disarankan untuk kembali dan memulai dari sana.
Dalam codelab ini, Anda akan membangun konsep yang telah dipelajari untuk menerapkan tata letak adaptif lebih lanjut di aplikasi Anda. Tata letak adaptif yang akan Anda terapkan adalah bagian dari tata letak kanonis - sekumpulan pola yang umum digunakan untuk tampilan layar besar. Anda juga akan mempelajari lebih banyak teknik pengujian dan alat untuk membantu Anda membangun aplikasi yang tangguh dengan cepat.
Prasyarat
- Menyelesaikan codelab Membangun aplikasi adaptif dengan navigasi dinamis
- Memahami pemrograman Kotlin, termasuk class, fungsi, dan kondisional
- Memahami class
ViewModel
- Memahami fungsi
Composable
- Pengalaman membuat tata letak dengan Jetpack Compose
- Pengalaman menjalankan aplikasi di perangkat atau emulator
- Pengalaman menggunakan
WindowSizeClass
API
Yang akan Anda pelajari
- Cara membuat tata letak adaptif pola tampilan daftar menggunakan Jetpack Compose
- Cara membuat pratinjau untuk berbagai ukuran layar
- Cara menguji kode untuk beberapa ukuran layar
Yang akan Anda bangun
- Anda akan terus mengupdate aplikasi Reply agar adaptif untuk semua ukuran layar.
Aplikasi final akan terlihat seperti ini:
Yang akan Anda butuhkan
- Komputer dengan akses internet, browser web, dan Android Studio
- Akses ke GitHub
Mendownload kode awal
Untuk memulai, download kode awal:
Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git $ cd basic-android-kotlin-compose-training-reply-app $ git checkout nav-update
Anda dapat menjelajahi kode awal di repositori GitHub Reply
.
2. Pratinjau untuk berbagai ukuran layar
Membuat pratinjau untuk berbagai ukuran layar
Dalam codelab Membangun aplikasi adaptif dengan navigasi dinamis, Anda telah mempelajari cara menggunakan composable pratinjau untuk membantu proses pengembangan. Untuk aplikasi adaptif, praktik terbaiknya adalah membuat beberapa pratinjau untuk menampilkan aplikasi di berbagai ukuran layar. Dengan beberapa pratinjau, Anda dapat melihat perubahan pada semua ukuran layar sekaligus. Selain itu, pratinjau juga berfungsi sebagai dokumentasi bagi developer lain yang meninjau kode Anda untuk melihat apakah aplikasi Anda kompatibel dengan berbagai ukuran layar.
Sebelumnya, Anda hanya memiliki satu pratinjau yang mendukung layar rapat. Berikutnya, Anda akan menambahkan lebih banyak pratinjau.
Untuk menambahkan pratinjau pada layar berukuran sedang dan yang diperluas, selesaikan langkah-langkah berikut:
- Tambahkan pratinjau untuk layar berukuran sedang dengan menetapkan nilai
widthDp
sedang dalam parameter anotasiPreview
dan menentukan nilaiWindowWidthSizeClass.Medium
sebagai parameter untuk composableReplyApp
.
MainActivity.kt
...
@Preview(showBackground = true, widthDp = 700)
@Composable
fun ReplyAppMediumPreview() {
ReplyTheme {
Surface {
ReplyApp(windowSize = WindowWidthSizeClass.Medium)
}
}
}
...
- Tambahkan pratinjau lain untuk layar yang diperluas dengan menetapkan nilai
widthDp
besar dalam parameter anotasiPreview
dan menentukan nilaiWindowWidthSizeClass.Expanded
sebagai parameter untuk composableReplyApp
.
MainActivity.kt
...
@Preview(showBackground = true, widthDp = 1000)
@Composable
fun ReplyAppExpandedPreview() {
ReplyTheme {
Surface {
ReplyApp(windowSize = WindowWidthSizeClass.Expanded)
}
}
}
...
- Bangun pratinjau untuk melihat hal berikut:
3. Mengimplementasikan tata letak konten adaptif
Pengantar tampilan daftar-detail
Anda mungkin memperhatikan bahwa konten pada layar yang diperluas terlihat membentang dan tidak memanfaatkan ruang layar yang tersedia dengan baik.
Anda dapat meningkatkan tata letak ini dengan menerapkan salah satu tata letak kanonis. Tata letak kanonis adalah komposisi layar besar yang berfungsi sebagai titik awal untuk desain dan implementasi. Anda dapat menggunakan tiga tata letak yang tersedia untuk memandu cara mengatur elemen umum dalam aplikasi, tampilan daftar, panel pendukung, dan feed. Setiap tata letak mempertimbangkan kasus penggunaan dan komponen umum untuk memenuhi ekspektasi dan kebutuhan pengguna terkait cara aplikasi beradaptasi di seluruh ukuran layar dan titik henti sementara.
Untuk aplikasi Reply, mari kita implementasikan tampilan detail daftar, karena yang terbaik adalah menjelajahi konten dan melihat detail dengan cepat. Dengan tata letak tampilan daftar-detail, Anda akan membuat panel lain di samping layar daftar email untuk menampilkan detail email. Tata letak ini memungkinkan Anda menggunakan layar yang tersedia untuk menampilkan lebih banyak informasi kepada pengguna dan membuat aplikasi menjadi lebih produktif.
Mengimplementasikan tampilan daftar-detail
Untuk menerapkan tampilan daftar-detail bagi layar yang diperluas, selesaikan langkah-langkah berikut:
- Untuk merepresentasikan berbagai jenis tata letak konten, di
WindowStateUtils.kt
, buat classEnum
baru untuk jenis konten yang berbeda. Gunakan nilaiLIST_AND_DETAIL
saat layar yang diperluas sedang digunakan danLIST_ONLY
jika tidak.
WindowStateUtils.kt
...
enum class ReplyContentType {
LIST_ONLY, LIST_AND_DETAIL
}
...
- Deklarasikan variabel
contentType
diReplyApp.kt
dan tetapkancontentType
yang sesuai untuk berbagai ukuran jendela guna membantu menentukan pilihan jenis konten yang sesuai, bergantung pada ukuran layar.
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyContentType
...
val navigationType: ReplyNavigationType
val contentType: ReplyContentType
when (windowSize) {
WindowWidthSizeClass.Compact -> {
...
contentType = ReplyContentType.LIST_ONLY
}
WindowWidthSizeClass.Medium -> {
...
contentType = ReplyContentType.LIST_ONLY
}
WindowWidthSizeClass.Expanded -> {
...
contentType = ReplyContentType.LIST_AND_DETAIL
}
else -> {
...
contentType = ReplyContentType.LIST_ONLY
}
}
...
Selanjutnya, Anda dapat menggunakan nilai contentType
untuk membuat cabang yang berbeda bagi tata letak dalam composable ReplyAppContent
.
- Di
ReplyHomeScreen.kt
, tambahkancontentType
sebagai parameter ke composableReplyHomeScreen
.
ReplyHomeScreen.kt
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
contentType: ReplyContentType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit,
onEmailCardPressed: (Email) -> Unit,
onDetailScreenBackPressed: () -> Unit,
modifier: Modifier = Modifier
) {
...
- Teruskan nilai
contentType
ke composableReplyHomeScreen
.
ReplyApp.kt
...
ReplyHomeScreen(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
...
- Tambahkan
contentType
sebagai parameter untuk composableReplyAppContent
.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
contentType: ReplyContentType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit),
onEmailCardPressed: (Email) -> Unit,
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- Teruskan nilai
contentType
ke dua composableReplyAppContent
.
ReplyHomeScreen.kt
...
ReplyAppContent(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
isFullScreen = true,
onBackButtonClicked = onDetailScreenBackPressed,
modifier = modifier
)
}
}
...
Mari kita tampilkan daftar lengkap dan layar detail saat contentType
adalah LIST_AND_DETAIL
atau hanya daftar konten email saat contentType
adalah LIST_ONLY
.
- Di
ReplyHomeScreen.kt
, tambahkan pernyataanif/else
di composableReplyAppContent
untuk menampilkan composableReplyListAndDetailContent
saat nilaicontentType
adalahLIST_AND_DETAIL
dan menampilkan composableReplyListOnlyContent
di cabangelse
.
ReplyHomeScreen.kt
...
Column(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.inverseOnSurface)
) {
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
ReplyListAndDetailContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
} else {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
.padding(
horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
)
)
}
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
...
- Hapus kondisi
replyUiState.isShowingHomepage
untuk menampilkan panel navigasi permanen, karena pengguna tidak perlu membuka tampilan detail jika menggunakan tampilan yang diperluas.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER) {
PermanentNavigationDrawer(
drawerContent = {
PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.background(MaterialTheme.colorScheme.inverseOnSurface)
.padding(dimensionResource(R.dimen.drawer_padding_content))
)
}
}
) {
...
- Jalankan aplikasi Anda pada mode tablet untuk melihat layar di bawah:
Meningkatkan elemen UI untuk tampilan daftar-detail
Saat ini, aplikasi Anda menampilkan panel detail di layar utama untuk layar yang diperluas.
Namun, layar berisi elemen yang tidak relevan, seperti tombol kembali, header subjek, dan padding tambahan, karena dirancang untuk layar detail mandiri. Anda dapat meningkatkan fitur ini di waktu berikutnya dengan penyesuaian sederhana.
Guna meningkatkan layar detail untuk tampilan yang diperluas, selesaikan langkah-langkah berikut:
- Di
ReplyDetailsScreen.kt
, tambahkan variabelisFullScreen
sebagai parameterBoolean
ke composableReplyDetailsScreen
.
Penambahan ini memungkinkan Anda membedakan composable saat menggunakannya sebagai composable mandiri dan saat Anda menggunakannya di layar utama.
ReplyDetailsScreen.kt
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
isFullScreen: Boolean = false
) {
...
- Di dalam composable
ReplyDetailsScreen
, gabungkan composableReplyDetailsScreenTopBar
dengan pernyataanif
sehingga hanya ditampilkan saat aplikasi dalam mode layar penuh.
ReplyDetailsScreen.kt
...
LazyColumn(
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.inverseOnSurface)
.padding(top = dimensionResource(R.dimen.detail_card_list_padding_top))
) {
item {
if (isFullScreen) {
ReplyDetailsScreenTopBar(
onBackPressed,
replyUiState,
Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.detail_topbar_padding_bottom))
)
)
}
...
Sekarang Anda dapat menambahkan padding. Padding yang diperlukan untuk composable ReplyEmailDetailsCard
berbeda-beda bergantung pada apakah Anda menggunakannya sebagai layar penuh atau tidak. Saat Anda menggunakan ReplyEmailDetailsCard
dengan composable lain di layar yang diperluas, ada padding tambahan dari composable lain.
- Teruskan nilai
isFullScreen
ke composableReplyEmailDetailsCard
. Teruskan pengubah dengan padding horizontalR.dimen.detail_card_outer_padding_horizontal
jika layar dalam mode layar penuh atau teruskan pengubah dengan padding akhirR.dimen.detail_card_outer_padding_horizontal
.
ReplyDetailsScreen.kt
...
item {
if (isFullScreen) {
ReplyDetailsScreenTopBar(
onBackPressed,
replyUiState,
Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.detail_topbar_padding_bottom))
)
)
}
ReplyEmailDetailsCard(
email = replyUiState.currentSelectedEmail,
mailboxType = replyUiState.currentMailbox,
isFullScreen = isFullScreen,
modifier = if (isFullScreen) {
Modifier.padding(horizontal = dimensionResource(R.dimen.detail_card_outer_padding_horizontal))
} else {
Modifier.padding(end = dimensionResource(R.dimen.detail_card_outer_padding_horizontal))
}
)
}
...
- Tambahkan nilai
isFullScreen
sebagai parameter ke composableReplyEmailDetailsCard
.
ReplyDetailsScreen.kt
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ReplyEmailDetailsCard(
email: Email,
mailboxType: MailboxType,
modifier: Modifier = Modifier,
isFullScreen: Boolean = false
) {
...
- Di dalam composable
ReplyEmailDetailsCard
, hanya tampilkan teks subjek email saat aplikasi tidak dalam mode layar penuh, karena tata letak layar penuh sudah menampilkan subjek email sebagai header. Jika menggunakan layar penuh, tambahkan pengatur jarak dengan tinggiR.dimen.detail_content_padding_top
.
ReplyDetailsScreen.kt
...
Column(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.detail_card_inner_padding))
) {
DetailsScreenHeader(
email,
Modifier.fillMaxWidth()
)
if (isFullScreen) {
Spacer(modifier = Modifier.height(dimensionResource(R.dimen.detail_content_padding_top)))
} else {
Text(
text = stringResource(email.subject),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
modifier = Modifier.padding(
top = dimensionResource(R.dimen.detail_content_padding_top),
bottom = dimensionResource(R.dimen.detail_expanded_subject_body_spacing)
),
)
}
Text(
text = stringResource(email.body),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
DetailsScreenButtonBar(mailboxType, displayToast)
}
...
- Di
ReplyHomeScreen.kt
, di dalam composableReplyHomeScreen
, teruskan nilaitrue
untuk parameterisFullScreen
saat membuat composableReplyDetailsScreen
dalam mode mandiri.
ReplyHomeScreen.kt
...
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
isFullScreen = true,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
...
- Jalankan aplikasi dalam mode tablet dan lihat tata letak berikut:
Menyesuaikan penanganan kembali untuk tampilan daftar-detail
Dengan layar yang diperluas, Anda tidak perlu menavigasi ke ReplyDetailsScreen
sama sekali. Sebagai gantinya, Anda ingin aplikasi menutup saat pengguna memilih tombol kembali. Dengan demikian, kita harus menyesuaikan pengendali kembali.
Ubah pengendali kembali dengan meneruskan fungsi activity.finish()
sebagai parameter onBackPressed
dari composable ReplyDetailsScreen
di dalam composable ReplyListAndDetailContent
.
ReplyHomeContent.kt
...
import android.app.Activity
import androidx.compose.ui.platform.LocalContext
...
val activity = LocalContext.current as Activity
ReplyDetailsScreen(
replyUiState = replyUiState,
modifier = Modifier.weight(1f),
onBackPressed = { activity.finish() }
)
...
4. Verifikasi untuk berbagai ukuran layar
Pedoman kualitas aplikasi perangkat layar besar
Untuk menciptakan pengalaman yang luar biasa dan konsisten bagi pengguna Android, penting untuk membangun dan menguji aplikasi Anda dengan mempertimbangkan kualitas. Anda dapat membaca Pedoman kualitas aplikasi inti untuk menentukan cara meningkatkan kualitas aplikasi Anda.
Agar dapat membangun aplikasi yang bagus untuk semua faktor bentuk, tinjau Pedoman kualitas aplikasi perangkat layar besar. Aplikasi Anda juga harus memenuhi Tingkat 3 - Persyaratan kesiapan perangkat layar besar.
Menguji aplikasi Anda secara manual untuk mengetahui kesiapan perangkat layar besar
Pedoman kualitas aplikasi memberikan rekomendasi dan prosedur perangkat pengujian untuk memeriksa kualitas aplikasi Anda. Mari kita lihat contoh pengujian yang relevan dengan aplikasi Reply.
Pedoman kualitas aplikasi di atas mengharuskan aplikasi untuk mempertahankan atau memulihkan statusnya setelah konfigurasi berubah. Panduan ini juga memberikan petunjuk tentang cara menguji aplikasi, seperti yang ditampilkan dalam gambar berikut:
Guna menguji aplikasi Reply secara manual untuk kontinuitas konfigurasi, selesaikan langkah-langkah berikut:
- Jalankan aplikasi Reply pada perangkat berukuran sedang atau, jika Anda menggunakan emulator yang dapat diubah ukurannya, dalam mode perangkat foldable yang dibentangkan.
- Pastikan Putar otomatis di emulator disetel ke aktif.
- Scroll daftar email ke bawah.
- Klik kartu email. Misalnya, buka email dari Ali.
- Putar perangkat untuk memeriksa apakah email yang dipilih masih konsisten dengan email yang dipilih dalam orientasi potret. Dalam contoh ini, email dari Ali masih ditampilkan.
- Putar kembali ke orientasi potret untuk memeriksa apakah aplikasi masih menampilkan email yang sama.
5. Menambahkan pengujian otomatis untuk aplikasi adaptif
Mengonfigurasi pengujian untuk ukuran layar yang rapat
Di codelab Menguji Aplikasi Cupcake, Anda telah mempelajari cara membuat pengujian UI. Sekarang, mari pelajari cara membuat pengujian tertentu untuk berbagai ukuran layar.
Di aplikasi Reply, Anda menggunakan elemen navigasi yang berbeda untuk ukuran layar yang berbeda. Misalnya, Anda akan melihat panel navigasi permanen saat pengguna melihat layar yang diperluas. Hal ini berguna untuk membuat pengujian guna memverifikasi keberadaan berbagai elemen navigasi, seperti navigasi bawah, kolom samping navigasi, dan panel navigasi untuk berbagai ukuran layar.
Guna membuat pengujian untuk memverifikasi keberadaan elemen navigasi bawah dalam layar yang rapat, lakukan langkah-langkah berikut:
- Dalam direktori pengujian, buat class Kotlin baru bernama
ReplyAppTest.kt
. - Di class
ReplyAppTest
, buat aturan pengujian menggunakancreateAndroidComposeRule
dan teruskanComponentActivity
sebagai parameter jenis.ComponentActivity
digunakan untuk mengakses aktivitas kosong, bukanMainActivity
.
ReplyAppTest.kt
...
class ReplyAppTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
...
Untuk membedakan antara elemen navigasi di layar, tambahkan testTag
di composable ReplyBottomNavigationBar
.
- Tentukan resource string untuk Navigation Bottom.
strings.xml
...
<resources>
...
<string name="navigation_bottom">Navigation Bottom</string>
...
</resources>
- Tambahkan nama string sebagai argumen
testTag
untuk metodetestTag
dariModifier
dalam composableReplyBottomNavigationBar
.
ReplyHomeScreen.kt
...
val bottomNavigationContentDescription = stringResource(R.string.navigation_bottom)
ReplyBottomNavigationBar(
...
modifier = Modifier
.fillMaxWidth()
.testTag(bottomNavigationContentDescription)
)
...
- Di class
ReplyAppTest
, buat fungsi pengujian untuk menguji layar berukuran rapat. Setel kontencomposeTestRule
dengan composableReplyApp
dan teruskanWindowWidthSizeClass.Compact
sebagai argumenwindowSize
.
ReplyAppTest.kt
...
@Test
fun compactDevice_verifyUsingBottomNavigation() {
// Set up compact window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact
)
}
}
- Nyatakan bahwa elemen navigasi bawah ada dengan tag pengujian. Panggil fungsi ekstensi
onNodeWithTagForStringId
dicomposeTestRule
dan teruskan string bawah navigasi, lalu panggil metodeassertExists()
.
ReplyAppTest.kt
...
@Test
fun compactDevice_verifyUsingBottomNavigation() {
// Set up compact window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact
)
}
// Bottom navigation is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_bottom
).assertExists()
}
- Jalankan pengujian dan pastikan pengujian berhasil.
Mengonfigurasi pengujian untuk ukuran layar sedang dan diperluas
Setelah Anda berhasil membuat pengujian untuk layar yang rapat, mari kita buat pengujian yang sesuai untuk layar berukuran sedang dan yang diperluas.
Untuk membuat pengujian guna memverifikasi keberadaan kolom samping navigasi dan panel navigasi permanen bagi layar berukuran sedang dan yang diperluas, lakukan langkah-langkah berikut:
- Tentukan resource string untuk Kolom Samping Navigasi yang akan digunakan sebagai tag pengujian di lain waktu.
strings.xml
...
<resources>
...
<string name="navigation_rail">Navigation Rail</string>
...
</resources>
- Teruskan string sebagai tag pengujian melalui
Modifier
di composablePermanentNavigationDrawer
.
ReplyHomeScreen.kt
...
val navigationDrawerContentDescription = stringResource(R.string.navigation_drawer)
PermanentNavigationDrawer(
...
modifier = Modifier.testTag(navigationDrawerContentDescription)
)
...
- Teruskan string sebagai tag pengujian melalui
Modifier
dalam composableReplyNavigationRail
.
ReplyHomeScreen.kt
...
val navigationRailContentDescription = stringResource(R.string.navigation_rail)
ReplyNavigationRail(
...
modifier = Modifier
.testTag(navigationRailContentDescription)
)
...
- Tambahkan pengujian untuk memverifikasi bahwa elemen rel navigasi ada di layar sedang.
ReplyAppTest.kt
...
@Test
fun mediumDevice_verifyUsingNavigationRail() {
// Set up medium window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Medium
)
}
// Navigation rail is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_rail
).assertExists()
}
- Tambahkan pengujian untuk memverifikasi bahwa elemen panel navigasi ada di layar yang diperluas.
ReplyAppTest.kt
...
@Test
fun expandedDevice_verifyUsingNavigationDrawer() {
// Set up expanded window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Expanded
)
}
// Navigation drawer is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_drawer
).assertExists()
}
- Gunakan emulator tablet atau emulator yang dapat diubah ukurannya dalam mode Tablet untuk menjalankan pengujian.
- Jalankan semua pengujian dan pastikan pengujian berhasil.
Menguji perubahan konfigurasi di layar yang rapat
Perubahan konfigurasi adalah kejadian umum yang terjadi dalam siklus proses aplikasi Anda. Misalnya, saat Anda mengubah orientasi dari potret ke lanskap, perubahan konfigurasi terjadi. Saat terjadi perubahan konfigurasi, penting untuk menguji apakah aplikasi Anda mempertahankan statusnya. Berikutnya, Anda akan membuat pengujian, yang menyimulasikan perubahan konfigurasi, untuk menguji apakah aplikasi mempertahankan statusnya di layar yang rapat.
Untuk menguji perubahan konfigurasi di layar yang rapat:
- Dalam direktori pengujian, buat class Kotlin baru bernama
ReplyAppStateRestorationTest.kt
. - Di class
ReplyAppStateRestorationTest
, buat aturan pengujian menggunakancreateAndroidComposeRule
dan teruskanComponentActivity
sebagai parameter jenis.
ReplyAppStateRestorationTest.kt
...
class ReplyAppStateRestorationTest {
/**
* Note: To access to an empty activity, the code uses ComponentActivity instead of
* MainActivity.
*/
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
}
...
- Buat fungsi pengujian untuk memverifikasi bahwa email masih dipilih di layar rapat setelah perubahan konfigurasi.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
}
...
Untuk menguji perubahan konfigurasi, Anda harus menggunakan StateRestorationTester
.
- Siapkan
stateRestorationTester
dengan meneruskancomposeTestRule
sebagai argumen keStateRestorationTester
. - Gunakan
setContent()
dengan composableReplyApp
dan teruskanWindowWidthSizeClass.Compact
sebagai argumenwindowSize
.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
}
...
- Pastikan bahwa email ketiga ditampilkan di aplikasi. Gunakan metode
assertIsDisplayed()
dicomposeTestRule
, yang mencari teks email ketiga.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
}
...
- Buka layar detail email dengan mengklik subjek email. Gunakan metode
performClick()
untuk menavigasi.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
}
...
- Pastikan bahwa email ketiga ditampilkan di layar detail. Nyatakan keberadaan tombol kembali untuk mengonfirmasi bahwa aplikasi berada di layar detail, dan pastikan bahwa teks email ketiga ditampilkan.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
}
...
- Simulasikan perubahan konfigurasi menggunakan
stateRestorationTester.emulateSavedInstanceStateRestore()
.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
}
...
- Pastikan kembali bahwa email ketiga ditampilkan di layar detail. Nyatakan keberadaan tombol kembali untuk mengonfirmasi bahwa aplikasi berada di layar detail, dan pastikan bahwa teks email ketiga ditampilkan.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
// Verify that it still shows the detailed screen for the same email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
}
...
- Jalankan pengujian dengan emulator ponsel atau emulator yang dapat diubah ukurannya dalam mode Ponsel.
- Pastikan pengujian berhasil.
Menguji perubahan konfigurasi di layar yang diperluas
Untuk menguji perubahan konfigurasi di layar yang diperluas dengan menyimulasikan perubahan konfigurasi dan meneruskan WindowWidthSizeClass yang sesuai, selesaikan langkah-langkah berikut:
- Buat fungsi pengujian untuk memverifikasi bahwa email masih dipilih di layar detail setelah perubahan konfigurasi.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
}
...
Untuk menguji perubahan konfigurasi, Anda harus menggunakan StateRestorationTester
.
- Siapkan
stateRestorationTester
dengan meneruskancomposeTestRule
sebagai argumen keStateRestorationTester
. - Gunakan
setContent()
dengan composableReplyApp
dan teruskanWindowWidthSizeClass.Expanded
sebagai argumenwindowSize
.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
}
...
- Pastikan bahwa email ketiga ditampilkan di aplikasi. Gunakan metode
assertIsDisplayed()
dicomposeTestRule
, yang mencari teks email ketiga.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
}
...
- Pilih email ketiga di layar detail. Gunakan metode
performClick()
untuk memilih email.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
...
}
...
- Verifikasi bahwa layar detail menampilkan email ketiga dengan menggunakan
testTag
pada layar detail dan mencari teks pada turunannya. Pendekatan ini memastikan bahwa Anda dapat menemukan teks di bagian detail dan bukan di daftar email.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
...
}
...
- Simulasikan perubahan konfigurasi menggunakan
stateRestorationTester.emulateSavedInstanceStateRestore()
.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
...
}
...
- Pastikan kembali bahwa layar detail menampilkan email ketiga setelah perubahan konfigurasi.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
// Verify that third email is still displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
}
...
- Jalankan pengujian dengan emulator tablet atau emulator yang dapat diubah ukurannya dalam mode Tablet.
- Pastikan pengujian berhasil.
Menggunakan anotasi untuk mengelompokkan pengujian pada berbagai ukuran layar
Anda mungkin menyadari dari pengujian sebelumnya bahwa beberapa pengujian gagal saat dijalankan di perangkat dengan ukuran layar yang tidak kompatibel. Meskipun dapat menjalankan pengujian satu per satu menggunakan perangkat yang sesuai, pendekatan ini mungkin tidak diskalakan jika ada banyak kasus pengujian.
Untuk mengatasi masalah ini, Anda dapat membuat anotasi untuk menunjukkan ukuran layar tempat pengujian dapat berjalan, dan mengonfigurasi pengujian yang dianotasi untuk perangkat yang sesuai.
Untuk menjalankan pengujian berdasarkan ukuran layar, selesaikan langkah-langkah berikut:
- Dalam direktori pengujian, buat
TestAnnotations.kt
, yang berisi tiga class anotasi:TestCompactWidth
,TestMediumWidth
,TestExpandedWidth
.
TestAnnotations.kt
...
annotation class TestCompactWidth
annotation class TestMediumWidth
annotation class TestExpandedWidth
...
- Gunakan anotasi pada fungsi pengujian untuk pengujian rapat dengan menempatkan anotasi
TestCompactWidth
setelah anotasi pengujian untuk pengujian rapat dalamReplyAppTest
danReplyAppStateRestorationTest
.
ReplyAppTest.kt
...
@Test
@TestCompactWidth
fun compactDevice_verifyUsingBottomNavigation() {
...
ReplyAppStateRestorationTest.kt
...
@Test
@TestCompactWidth
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
- Gunakan anotasi pada fungsi pengujian untuk pengujian sedang dengan menempatkan anotasi
TestMediumWidth
setelah anotasi pengujian untuk pengujian sedang diReplyAppTest
.
ReplyAppTest.kt
...
@Test
@TestMediumWidth
fun mediumDevice_verifyUsingNavigationRail() {
...
- Gunakan anotasi pada fungsi pengujian untuk pengujian yang diperluas dengan menempatkan anotasi
TestExpandedWidth
setelah anotasi pengujian untuk pengujian yang diperluas diReplyAppTest
danReplyAppStateRestorationTest
.
ReplyAppTest.kt
...
@Test
@TestExpandedWidth
fun expandedDevice_verifyUsingNavigationDrawer() {
...
ReplyAppStateRestorationTest.kt
...
@Test
@TestExpandedWidth
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
Untuk memastikan keberhasilan, konfigurasi pengujian agar hanya menjalankan pengujian yang dianotasi dengan TestCompactWidth
.
- Di Android Studio, pilih Run > Edit Configurations...
- Ganti nama pengujian sebagai Compact tests, lalu pilih untuk menjalankan pengujian All in Package.
- Klik titik tiga (...) di sebelah kanan kolom Instrumentation arguments.
- Klik tombol plus (
+
) dan tambahkan parameter tambahan: annotation dengan nilai com.example.reply.test.TestCompactWidth.
- Jalankan pengujian dengan emulator rapat.
- Pastikan hanya pengujian rapat yang dijalankan.
- Ulangi langkah-langkah tersebut untuk layar sedang dan yang diperluas.
6. Mendapatkan kode solusi
Untuk mendownload kode codelab yang sudah selesai, gunakan perintah git berikut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi, lihat di GitHub.
7. Kesimpulan
Selamat! Anda telah membuat aplikasi Reply menjadi adaptif untuk semua ukuran layar dengan menerapkan tata letak adaptif. Anda juga belajar mempercepat pengembangan menggunakan pratinjau dan mempertahankan kualitas aplikasi menggunakan berbagai metode pengujian.
Jangan lupa untuk membagikan karya Anda di media sosial dengan #AndroidBasics.