Membangun aplikasi adaptif dengan navigasi dinamis

1. Pengantar

Salah satu keuntungan besar pengembangan aplikasi Anda di platform Android adalah peluang besar untuk menjangkau pengguna dalam berbagai jenis faktor bentuk, seperti perangkat wearable, perangkat foldable, tablet, desktop, dan bahkan TV. Saat menggunakan aplikasi, pengguna Anda mungkin ingin menggunakan aplikasi yang sama pada perangkat layar besar untuk memanfaatkan peningkatan properti. Pengguna Android yang menggunakan aplikasi mereka di beberapa perangkat dengan berbagai ukuran layar makin banyak, dan mereka mengharapkan pengalaman pengguna berkualitas tinggi di semua perangkat.

Sejauh ini, Anda telah belajar membuat aplikasi yang utamanya untuk perangkat seluler. Dalam codelab ini, Anda akan mempelajari cara mengubah aplikasi agar adaptif terhadap ukuran layar lainnya. Anda akan menggunakan pola tata letak navigasi adaptif yang bagus dan dapat digunakan untuk perangkat seluler maupun perangkat layar besar, seperti perangkat foldable, tablet, dan desktop.

Prasyarat

  • Memahami berbagai aspek terkait pemrograman Kotlin, termasuk class, fungsi, dan kondisional
  • Pemahaman dalam menggunakan class ViewModel
  • Pemahaman dalam membuat fungsi Composable
  • Pengalaman membuat tata letak dengan Jetpack Compose
  • Pengalaman menjalankan aplikasi di perangkat atau emulator

Yang akan Anda pelajari

  • Cara membuat navigasi antarlayar tanpa Grafik Navigasi untuk aplikasi sederhana
  • Cara membuat tata letak navigasi adaptif menggunakan Jetpack Compose
  • Cara membuat pengendali kembali kustom

Yang akan Anda build

  • Anda akan menerapkan navigasi dinamis di aplikasi Reply yang ada agar tata letaknya beradaptasi dengan semua ukuran layar

Produk akhirnya akan terlihat seperti gambar di bawah ini:

56cfa13ef31d0b59.png

​​

Yang akan Anda butuhkan

  • Komputer dengan akses internet, browser web, dan Android Studio
  • Akses ke GitHub

2. Ringkasan aplikasi

Pengantar aplikasi Reply

Aplikasi Reply adalah aplikasi multilayar yang menyerupai program email.

a1af0f9193718abf.png

Aplikasi tersebut berisi 4 kategori berbeda yang ditampilkan oleh tab yang berbeda, yaitu: kotak masuk, terkirim, draf, dan spam.

Mendownload kode awal

Di Android Studio, buka folder basic-android-kotlin-compose-training-reply-app.

3. Panduan kode awal

Direktori penting di aplikasi Reply

Direktori file Aplikasi Reply menampilkan dua sub-direktori yang diperluas:

Lapisan data dan UI project aplikasi Reply dipisahkan ke dalam berbagai direktori. ReplyViewModel, ReplyUiState, dan composable lainnya terletak di direktori ui. Class data dan enum yang menentukan lapisan data dan class penyedia data terletak di direktori data.

Inisialisasi data di aplikasi Reply

Aplikasi Reply dilakukan inisialisasi dengan data melalui metode initializeUIState() di ReplyViewModel, yang dijalankan dalam fungsi init.

ReplyViewModel.kt

...
    init {
        initializeUIState()
    }
 

    private fun initializeUIState() {
        var mailboxes: Map<MailboxType, List<Email>> =
            LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
        _uiState.value = ReplyUiState(
            mailboxes = mailboxes,
            currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
                ?: LocalEmailsDataProvider.defaultEmail
        )
    }
...

Composable tingkat layar

Seperti aplikasi lainnya, aplikasi Reply menggunakan composable ReplyApp sebagai composable utama tempat viewModel dan uiState dideklarasikan. Berbagai fungsi viewModel() juga diteruskan sebagai argumen lambda untuk composable ReplyHomeScreen.

ReplyApp.kt

...
@Composable
fun ReplyApp(modifier: Modifier = Modifier) {
    val viewModel: ReplyViewModel = viewModel()
    val replyUiState = viewModel.uiState.collectAsState().value

    ReplyHomeScreen(
        replyUiState = replyUiState,
        onTabPressed = { mailboxType: MailboxType ->
            viewModel.updateCurrentMailbox(mailboxType = mailboxType)
            viewModel.resetHomeScreenStates()
        },
        onEmailCardPressed = { email: Email ->
            viewModel.updateDetailsScreenStates(
                email = email
            )
        },
        onDetailScreenBackPressed = {
            viewModel.resetHomeScreenStates()
        },
        modifier = modifier
    )
}

Composable lainnya

  • ReplyHomeScreen.kt: berisi composable layar untuk layar utama, termasuk elemen navigasi.
  • ReplyHomeContent.kt: berisi composable yang menentukan composable layar utama yang lebih mendetail.
  • ReplyDetailsScreen.kt: berisi composable layar dan composable yang lebih kecil untuk layar detail.

Anda dapat membaca setiap file secara mendetail agar dapat lebih memahami composable sebelum melanjutkan ke bagian codelab berikutnya.

4. Mengubah layar tanpa grafik navigasi

Di jalur sebelumnya, Anda telah mempelajari cara menggunakan class NavHostController untuk berpindah dari satu layar ke layar lainnya. Dengan Compose, Anda juga dapat mengubah layar dengan pernyataan bersyarat sederhana dengan menggunakan status runtime yang dapat diubah. Ini sangat berguna terutama pada aplikasi kecil seperti aplikasi Reply, karena Anda hanya ingin beralih di antara dua layar.

Mengubah layar dengan perubahan status

Di Compose, layar dikomposisi ulang saat terjadi perubahan status. Anda dapat mengubah layar menggunakan kondisional sederhana untuk merespons perubahan status.

Anda akan menggunakan kondisional untuk menampilkan konten layar utama saat pengguna berada di layar utama, dan layar detail saat pengguna tidak berada di layar utama.

Modifikasi aplikasi Reply untuk mengizinkan perubahan layar saat status berubah dengan menyelesaikan langkah berikut:

  1. Buka kode awal di Android Studio.
  2. Di composable ReplyHomeScreen di ReplyHomeScreen.kt, gabungkan composable ReplyAppContent dengan pernyataan if saat properti isShowingHomepage dari objek replyUiState adalah true.

ReplyHomeScreen.kt

@Composable
fun ReplyHomeScreen(
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit,
    onEmailCardPressed: (Int) -> Unit,
    onDetailScreenBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) {

...
    if (replyUiState.isShowingHomepage) {
        ReplyAppContent(
            replyUiState = replyUiState,
            onTabPressed = onTabPressed,
            onEmailCardPressed = onEmailCardPressed,
            navigationItemContentList = navigationItemContentList,
            modifier = modifier

        )
    }
}

Anda kini harus memperhitungkan skenario saat pengguna tidak ada di layar utama dengan menampilkan layar detail.

  1. Tambahkan cabang else dengan composable ReplyDetailsScreen di isinya. Menambahkan replyUIState, onDetailScreenBackPressed, dan modifier sebagai argumen untuk composable ReplyDetailsScreen.

ReplyHomeScreen.kt

@Composable
fun ReplyHomeScreen(
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit,
    onEmailCardPressed: (Int) -> Unit,
    onDetailScreenBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) {

...

    if (replyUiState.isShowingHomepage) {
        ReplyAppContent(
            replyUiState = replyUiState,
            onTabPressed = onTabPressed,
            onEmailCardPressed = onEmailCardPressed,
            navigationItemContentList = navigationItemContentList,
            modifier = modifier

        )
    } else {
        ReplyDetailsScreen(
            replyUiState = replyUiState,
            onBackPressed = onDetailScreenBackPressed,
            modifier = modifier
        )
    }
}

Objek replyUiState adalah objek status. Dengan demikian, jika ada perubahan pada properti isShowingHomepage dari objek replyUiState, composable ReplyHomeScreen akan dibuat ulang dan pernyataan if/else akan dievaluasi ulang saat runtime. Pendekatan ini mendukung navigasi antarlayar yang berbeda tanpa menggunakan class NavHostController.

8443a3ef1a239f6e.gif

Membuat handler kembali kustom

Salah satu keuntungan menggunakan composable NavHost untuk beralih antarlayar adalah rute layar sebelumnya disimpan di data sebelumnya. Layar yang disimpan ini memungkinkan tombol kembali sistem menavigasi dengan mudah ke layar sebelumnya saat dipanggil. Karena aplikasi Reply tidak menggunakan NavHost, Anda harus menambahkan kode untuk menangani tombol kembali secara manual. Anda akan melakukannya nanti.

Selesaikan langkah-langkah berikut untuk membuat pengendali kembali kustom di aplikasi Reply:

  1. Pada baris pertama composable ReplyDetailsScreen, tambahkan composable BackHandler.
  2. Panggil fungsi onBackPressed() dalam isi composable BackHandler.

ReplyDetailsScreen.kt

...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
    replyUiState: ReplyUiState,
    onBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) {
    BackHandler {
        onBackPressed()
    }
... 

5. Menjalankan aplikasi di perangkat layar besar

Memeriksa aplikasi dengan emulator yang dapat diubah ukurannya

Untuk membuat aplikasi yang dapat digunakan, developer harus memahami pengalaman pengguna dalam berbagai faktor bentuk. Oleh karena itu, Anda harus menguji aplikasi pada berbagai faktor bentuk dari awal proses pengembangan.

Anda dapat menggunakan banyak emulator dengan berbagai ukuran layar untuk mencapai sasaran ini. Namun, melakukannya bisa jadi rumit, terutama saat Anda mem-build untuk beberapa ukuran layar sekaligus. Anda mungkin juga perlu menguji bagaimana aplikasi yang berjalan merespons perubahan ukuran layar, seperti perubahan orientasi, perubahan ukuran jendela di desktop, dan perubahan status lipat di perangkat foldable.

Android Studio membantu Anda menguji skenario ini dengan memperkenalkan emulator yang dapat diubah ukurannya.

Selesaikan langkah-langkah berikut untuk menyiapkan emulator yang dapat diubah ukurannya:

  1. Di Android Studio, pilih Tools > Device Manager.

Menu Tools menampilkan daftar opsi. Device Manager yang dipilih dan muncul di separuh bagian bawah daftar.

  1. Di Device Manager, klik ikon + untuk membuat perangkat virtual.

Toolbar Device Manager yang menampilkan dua opsi menu, termasuk opsi untuk membuat perangkat virtual.

  1. Pilih kategori Phone dan perangkat Resizable (Experimental).
  2. Klik Berikutnya.

Jendela Device Manager menampilkan permintaan untuk memilih definisi perangkat. Daftar opsi akan ditampilkan dengan kolom penelusuran di atasnya. Kategori

  1. Pilih API Level 34 atau yang lebih tinggi.
  2. Klik Next.

Jendela Virtual Device Configuration menampilkan perintah untuk memilih image sistem. Level API 34 yang dipilih.

  1. Beri nama Android Virtual Device baru Anda.
  2. Klik Finish.

Layar Konfigurasi Virtual di Android Virtural Device (AVD) akan ditampilkan. Layar konfigurasi menyertakan kolom teks untuk memasukkan nama AVD. Di bawah kolom nama terdapat daftar opsi perangkat, termasuk definisi perangkat (Resizable Experimental), image sistem (Tiramisu), dan orientasi, dengan orientasi Potret yang dipilih secara default. Pembacaan tombol

Menjalankan aplikasi di emulator layar besar

Setelah menyiapkan emulator yang dapat diubah ukurannya, mari kita lihat tampilan aplikasi di layar besar.

  1. Jalankan aplikasi di emulator yang dapat diubah ukurannya.
  2. Pilih Tablet untuk mode tampilan.

bfacf9c20a30b06b.png

  1. Periksa aplikasi dalam mode Tablet dengan mode lanskap.

bb0fa5e954f6ca4b.png

Perhatikan bahwa tampilan layar tablet dibuat panjang secara horizontal. Meskipun fungsional, orientasi ini mungkin bukan penggunaan real estate layar besar yang terbaik. Mari kita bahas masalah ini berikutnya.

Desain untuk perangkat layar besar

Saat pertama kali melihat aplikasi ini di tablet, Anda mungkin berpikir bahwa desainnya buruk dan tidak menarik. Tepat sekali: tata letak ini tidak didesain untuk penggunaan pada perangkat layar besar.

Saat membuat desain untuk perangkat layar besar, seperti tablet dan perangkat foldable, Anda harus mempertimbangkan ergonomi pengguna dan kedekatan jari pengguna dengan layar. Pada perangkat seluler, jari pengguna dapat dengan mudah menjangkau sebagian besar layar; lokasi elemen interaktif, seperti tombol dan elemen navigasi, tidak terlalu penting. Namun, untuk layar besar, adanya elemen interaktif penting di bagian tengah layar dapat membuatnya sulit dijangkau.

Seperti yang Anda lihat di aplikasi Reply, desain untuk perangkat layar besar tidak hanya meregangkan atau memperbesar elemen UI agar sesuai dengan layar. Ini adalah peluang untuk menggunakan properti yang ditingkatkan untuk menciptakan pengalaman yang berbeda bagi pengguna Anda. Misalnya, Anda dapat menambahkan tata letak lain di layar yang sama agar pengguna tidak perlu membuka layar lain atau dapat melakukan multitasking.

f50e77a4ffd923a.png

Desain ini dapat meningkatkan produktivitas pengguna dan mendorong interaksi yang lebih besar. Namun, sebelum men-deploy desain ini, Anda harus terlebih dahulu mempelajari cara membuat tata letak yang berbeda untuk ukuran layar yang berbeda.

6. Membuat tata letak beradaptasi dengan berbagai ukuran layar

Apa itu titik henti sementara?

Anda mungkin bertanya-tanya bagaimana Anda dapat menampilkan berbagai tata letak untuk aplikasi yang sama. Jawaban singkatnya adalah dengan menggunakan kondisional pada berbagai status, seperti yang Anda lakukan di awal codelab ini.

Untuk membuat aplikasi adaptif, Anda memerlukan tata letak untuk berubah berdasarkan ukuran layar. Titik pengukuran yang perubahan tata letaknya dikenal sebagai titik henti sementara. Desain Material membuat rentang titik henti sementara yang tidak sesuai yang mencakup sebagian besar layar Android.

Tabel yang berisi rentang titik henti sementara (dalam dp) untuk berbagai jenis dan penyiapan perangkat. 0 hingga 599 dp adalah untuk handset dalam mode potret, ponsel dalam mode lanskap, ukuran jendela yang rapat, 4 kolom, dan 8 margin minimum. 600 hingga 839 dp adalah untuk tablet kecil yang dapat dilipat dalam mode portait atau lanskap, class ukuran jendela sedang, 12 kolom, dan 12 margin minimum. 840 dp atau lebih besar untuk tablet besar dalam mode potret atau lanskap, class ukuran jendela yang diperluas, 12 kolom, dan 32 margin minimum. Catatan tabel menyatakan bahwa margin dan gutter fleksibel dan tidak perlu sama ukurannya dan bahwa ponsel dalam lanskap dianggap sebagai pengecualian agar tetap pas dalam rentang titik henti sementara 0 hingga 599 dp.

Tabel rentang titik henti sementara ini menunjukkan, misalnya, jika aplikasi Anda saat ini berjalan di perangkat dengan ukuran layar kurang dari 600 dp, Anda harus menampilkan tata letak seluler.

Menggunakan Window Size Class

WindowSizeClass API yang diperkenalkan untuk Compose membuat penerapan titik henti sementara Desain Material lebih sederhana.

Window Size Class memperkenalkan tiga kategori ukuran: Ringkas, Sedang, dan Diperluas, untuk lebar dan tinggi.

Diagram ini mewakili class ukuran jendela berbasis lebar. Diagram ini mewakili class ukuran jendela berbasis tinggi.

Selesaikan langkah berikut untuk menerapkan WindowSizeClass API di aplikasi Reply:

  1. Tambahkan dependensi material3-window-size-class ke file build.gradle.kts modul.

build.gradle.kts

...
dependencies {
...
    implementation("androidx.compose.material3:material3-window-size-class")
...
  1. Klik Sync Now untuk menyinkronkan gradle setelah menambahkan dependensi.

b4c912a45fa8b7f4.png

Dengan file build.gradle.kts terbaru, Anda kini dapat membuat variabel yang menyimpan ukuran jendela aplikasi pada waktu tertentu.

  1. Pada fungsi onCreate() di file MainActivity.kt, tetapkan metode calculateWindowSizeClass() dengan konteks this yang diteruskan dalam parameter ke variabel bernama windowSize.
  2. Impor paket calculateWindowSizeClass yang sesuai.

MainActivity.kt

...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass

...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val layoutDirection = LocalLayoutDirection.current
            Surface (
               // ...
            ) {
                val windowSize = calculateWindowSizeClass(this)
                ReplyApp()
...  
  1. Perhatikan garis bawah merah sintaksis calculateWindowSizeClass, yang menampilkan bohlam merah. Klik bohlam merah di sebelah kiri variabel windowSize dan pilih Opt in for ‘ExperimentalMaterial3WindowSizeClassApi' di ‘onCreate' untuk membuat anotasi di atas metode onCreate().

f8029f61dfad0306.png

Anda dapat menggunakan variabel WindowWidthSizeClass di MainActivity.kt untuk menentukan tata letak mana yang akan ditampilkan di berbagai composable. Mari siapkan composable ReplyApp untuk menerima nilai ini.

  1. Dalam file ReplyApp.kt, ubah composable ReplyApp untuk menerima WindowWidthSizeClass sebagai parameter dan impor paket yang sesuai.

ReplyApp.kt

...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...

@Composable
fun ReplyApp(
    windowSize: WindowWidthSizeClass,
    modifier: Modifier = Modifier
) {
...  
  1. Teruskan variabel windowSize ke komponen ReplyApp dalam metode onCreate() file MainActivity.kt.

MainActivity.kt

...
        setContent {
            ReplyTheme {
                Surface {
                    val windowSize = calculateWindowSizeClass(this)
                    ReplyApp(
                        windowSize = windowSize.widthSizeClass
                    )
...  

Anda juga harus memperbarui pratinjau aplikasi untuk parameter windowSize.

  1. Teruskan WindowWidthSizeClass.Compact sebagai parameter windowSize ke composable ReplyApp untuk komponen pratinjau, lalu impor paket yang sesuai.

MainActivity.kt

...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...

@Preview(showBackground = true)
@Composable
fun ReplyAppCompactPreview() {
    ReplyTheme {
        Surface {
            ReplyApp(
                windowSize = WindowWidthSizeClass.Compact,
            )
        }
    }
}
  1. Untuk mengubah tata letak aplikasi berdasarkan ukuran layar, tambahkan pernyataan when dalam composable ReplyApp berdasarkan nilai WindowWidthSizeClass.

ReplyApp.kt

...

@Composable
fun ReplyApp(
    windowSize: WindowWidthSizeClass,
    modifier: Modifier = Modifier
) {
    val viewModel: ReplyViewModel = viewModel()
    val replyUiState = viewModel.uiState.collectAsState().value
    
    when (windowSize) {
        WindowWidthSizeClass.Compact -> {
        }
        WindowWidthSizeClass.Medium -> {
        }
        WindowWidthSizeClass.Expanded -> {
        }
        else -> {
        }
    }
...  

Pada tahap ini, Anda telah menetapkan dasar untuk menggunakan nilai WindowSizeClass guna mengubah tata letak di aplikasi. Langkah berikutnya adalah menentukan tampilan aplikasi yang Anda inginkan pada ukuran layar yang berbeda.

7. Mengimplementasikan tata letak navigasi adaptif

Mengimplementasikan navigasi UI adaptif

Saat ini, navigasi bawah digunakan untuk semua ukuran layar.

f39984211e4dd665.png

Seperti yang telah dibahas sebelumnya, elemen navigasi ini tidak ideal karena pengguna dapat mengalami kesulitan untuk menjangkau elemen navigasi yang penting ini di layar yang lebih besar. Untungnya, ada pola yang direkomendasikan untuk berbagai elemen navigasi untuk berbagai class ukuran jendela di navigasi untuk UI yang responsif. Untuk aplikasi Reply, Anda dapat menerapkan elemen berikut:

Tabel mencantumkan class ukuran jendela dan beberapa item yang ditampilkan. Lebar rapat akan menampilkan menu navigasi bawah. Lebar sedang menampilkan kolom samping navigasi. Lebar yang diperluas menampilkan panel navigasi persisten dengan tepi depan.

Kolom samping navigasi adalah komponen navigasi lain dari desain material yang memungkinkan opsi navigasi ringkas agar tujuan utama dapat diakses dari sisi aplikasi.

1c73d20ace67811c.png

Demikian pula, panel navigasi persisten/permanen dibuat oleh desain material sebagai opsi lain untuk memberikan akses ergonomis pada layar yang lebih besar.

6795fb31e6d4a564.png

Mengimplementasikan panel navigasi

Untuk membuat panel navigasi bagi layar yang diperluas, Anda dapat menggunakan parameter navigationType. Selesaikan langkah-langkah berikut untuk melakukannya:

  1. Untuk merepresentasikan berbagai jenis elemen navigasi, buat file baru WindowStateUtils.kt dalam paket utils baru, yang berada di direktori ui.
  2. Tambahkan class Enum untuk mewakili berbagai jenis elemen navigasi.

WindowStateUtils.kt

package com.example.reply.ui.utils

enum class ReplyNavigationType {
    BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}
 

Agar berhasil menerapkan panel navigasi, Anda perlu menentukan jenis navigasi berdasarkan ukuran jendela aplikasi.

  1. Pada composable ReplyApp, buat variabel navigationType dan tetapkan nilai ReplyNavigationType yang sesuai, sesuai dengan ukuran layar dalam pernyataan when.

ReplyApp.kt

...
import com.example.reply.ui.utils.ReplyNavigationType
...
    val navigationType: ReplyNavigationType
    when (windowSize) {
        WindowWidthSizeClass.Compact -> {
            navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
        }
        WindowWidthSizeClass.Medium -> {
            navigationType = ReplyNavigationType.NAVIGATION_RAIL
        }
        WindowWidthSizeClass.Expanded -> {
            navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        }
        else -> {
            navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
        }
    }
...
 

Anda dapat menggunakan nilai navigationType dalam composable ReplyHomeScreen. Anda dapat mempersiapkannya dengan menjadikannya parameter untuk composable.

  1. Pada composable ReplyHomeScreen, tambahkan navigationType sebagai parameter.

ReplyHomeScreen.kt

...
@Composable
fun ReplyHomeScreen(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit,
    onEmailCardPressed: (Email) -> Unit,
    onDetailScreenBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) 

...
 
  1. Teruskan navigationType ke composable ReplyHomeScreen.

ReplyApp.kt

...
    ReplyHomeScreen(
        navigationType = navigationType,
        replyUiState = replyUiState,
        onTabPressed = { mailboxType: MailboxType ->
            viewModel.updateCurrentMailbox(mailboxType = mailboxType)
            viewModel.resetHomeScreenStates()
        },
        onEmailCardPressed = { email: Email ->
            viewModel.updateDetailsScreenStates(
                email = email
            )
        },
        onDetailScreenBackPressed = {
            viewModel.resetHomeScreenStates()
        },
        modifier = modifier
    )
...
 

Selanjutnya, Anda dapat membuat cabang untuk menampilkan konten aplikasi dengan panel navigasi saat pengguna membuka aplikasi pada layar yang diperluas dan menampilkan layar utama.

  1. Dalam isi composable ReplyHomeScreen, tambahkan pernyataan if untuk kondisi navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage.

ReplyHomeScreen.kt

import androidx.compose.material3.PermanentNavigationDrawer
...
@Composable
fun ReplyHomeScreen(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit,
    onEmailCardPressed: (Email) -> Unit,
    onDetailScreenBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) {
...
    if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
    ) {
    }

    if (replyUiState.isShowingHomepage) {
        ReplyAppContent(
            replyUiState = replyUiState,
...
  1. Untuk membuat panel samping permanen, buat composable PermanentNavigationDrawer dalam isi pernyataan if dan tambahkan composable NavigationDrawerContent sebagai input untuk parameter drawerContent.
  2. Tambahkan composable ReplyAppContent sebagai argumen lambda final dari PermanentNavigationDrawer.

ReplyHomeScreen.kt

...
    if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
    ) {
        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))
                    )
                }
            }
        ) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        }
    }

...
  1. Tambahkan cabang else yang menggunakan isi composable sebelumnya untuk mempertahankan cabang sebelumnya untuk layar yang tidak diperluas.

ReplyHomeScreen.kt

...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
) {
        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))
                    )
                }
            }
        ) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        }
    } else {
        if (replyUiState.isShowingHomepage) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        } else {
            ReplyDetailsScreen(
                replyUiState = replyUiState,
                onBackPressed = onDetailScreenBackPressed,
                modifier = modifier
            )
        }
    }
}
...
  1. Jalankan aplikasi dalam mode Tablet. Anda akan melihat layar berikut:

2dbbc2f88d08f6a.png

Mengimplementasikan kolom samping navigasi

Serupa dengan implementasi panel navigasi, Anda harus menggunakan parameter navigationType untuk beralih di antara elemen navigasi.

Pertama, mari kita tambahkan kolom samping navigasi untuk layar berukuran sedang.

  1. Mulai dengan menyiapkan composable ReplyAppContent dengan menambahkan navigationType sebagai parameter.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit),
    onEmailCardPressed: (Email) -> Unit,
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {       
... 
  1. Teruskan nilai navigationType ke kedua composable ReplyAppContent.

ReplyHomeScreen.kt

...
            ReplyAppContent(
                navigationType = navigationType,
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        }
    } else {
        if (replyUiState.isShowingHomepage) {
            ReplyAppContent(
                navigationType = navigationType,
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
... 

Selanjutnya, mari kita tambahkan cabang, yang memungkinkan aplikasi menampilkan kolom samping navigasi untuk beberapa skenario.

  1. Di baris pertama isi composable ReplyAppContent, gabungkan composable ReplyNavigationRail di sekitar composable AnimatedVisibility dan setel parameter visible menjadi true jika nilai ReplyNavigationType adalah NAVIGATION_RAIL.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit),
    onEmailCardPressed: (Email) -> Unit,
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {
    Box(modifier = modifier) {
        AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
            ReplyNavigationRail(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
            )
        }
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    MaterialTheme.colorScheme.inverseOnSurface
            )
        ) {
            ReplyListOnlyContent(
                replyUiState = replyUiState,
                onEmailCardPressed = onEmailCardPressed,
                modifier = Modifier.weight(1f)
                    .padding(
                        horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
                    )
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList,
                  modifier = Modifier
                      .fillMaxWidth()
            )
        }
    }
}     
... 
  1. Untuk meratakan composable dengan benar, gabungkan composable AnimatedVisibility dan composable Column yang ditemukan dalam isi ReplyAppContent di composable Row.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit),
    onEmailCardPressed: (Email) -> Unit,
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier,
) {
    Row(modifier = modifier) {
        AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
            val navigationRailContentDescription = stringResource(R.string.navigation_rail)
            ReplyNavigationRail(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList
            )
        }
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.inverseOnSurface)
        ) {
            ReplyListOnlyContent(
                replyUiState = replyUiState,
                onEmailCardPressed = onEmailCardPressed,
                modifier = Modifier.weight(1f)
                    .padding(
                        horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
                )
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = Modifier
                    .fillMaxWidth()
            )
        }
    }
}

... 

Terakhir, pastikan navigasi bawah ditampilkan dalam beberapa skenario.

  1. Setelah composable ReplyListOnlyContent, gabungkan composable ReplyBottomNavigationBar dengan composable AnimatedVisibility.
  2. Tetapkan parameter visible saat nilai ReplyNavigationType adalah BOTTOM_NAVIGATION.

ReplyHomeScreen.kt

...
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) {
    val bottomNavigationContentDescription = stringResource(R.string.navigation_bottom)
    ReplyBottomNavigationBar(
        currentTab = replyUiState.currentMailbox,
        onTabPressed = onTabPressed,
        navigationItemContentList = navigationItemContentList,
        modifier = Modifier
            .fillMaxWidth()
    )
}

... 
  1. Jalankan aplikasi dalam mode perangkat foldable yang ditutup. Anda akan melihat layar berikut:

bfacf9c20a30b06b.png

8. Mendapatkan kode solusi

Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:

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

Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi, lihat di GitHub.

9. Kesimpulan

Selamat! Anda selangkah lebih dekat untuk membuat aplikasi Reply dapat menyesuaikan semua ukuran layar dengan menerapkan tata letak navigasi adaptif. Anda meningkatkan pengalaman pengguna menggunakan banyak faktor bentuk Android. Pada codelab berikutnya, Anda akan meningkatkan keterampilan menggunakan aplikasi adaptif dengan menerapkan tata letak, pengujian, dan pratinjau konten adaptif.

Jangan lupa untuk membagikan karya Anda di media sosial dengan #AndroidBasics.

Pelajari lebih lanjut