Menavigasi antarlayar dengan Compose

1. Sebelum memulai

Hingga saat ini, aplikasi yang telah Anda kerjakan terdiri dari satu layar. Namun, banyak aplikasi yang Anda gunakan mungkin memiliki beberapa layar yang dapat dinavigasi. Misalnya, aplikasi Setelan memiliki banyak halaman konten yang tersebar di berbagai layar.

Halaman pertama di aplikasi Setelan Android.

Halaman setelan setelah pengguna memilih "Perangkat Terhubung" di halaman pertama.

Halaman setelan setelah pengguna memilih "Sambungkan Perangkat Baru" di halaman sebelumnya.

Dalam pengembangan Android modern, aplikasi multilayar dibuat menggunakan komponen Jetpack Navigation. Komponen Navigation Compose memungkinkan Anda mem-build aplikasi multilayar di Compose dengan mudah menggunakan pendekatan deklaratif, seperti mem-build antarmuka pengguna. Codelab ini memperkenalkan dasar-dasar komponen Navigation Compose, cara membuat AppBar responsif, dan cara mengirim data dari aplikasi Anda ke aplikasi lain menggunakan intent, semuanya dilakukan sekaligus menunjukkan praktik terbaik dalam aplikasi yang semakin kompleks.

Prasyarat

  • Pemahaman tentang bahasa Kotlin, termasuk jenis fungsi, lambda, dan fungsi cakupan
  • Pemahaman tentang tata letak Row dan Column dasar di Compose

Yang akan Anda pelajari

  • Membuat composable NavHost untuk menentukan rute dan layar di aplikasi Anda.
  • Menavigasi antarlayar menggunakan NavHostController.
  • Memanipulasi data sebelumnya untuk beralih ke layar sebelumnya.
  • Menggunakan intent untuk berbagi data dengan aplikasi lain.
  • Menyesuaikan AppBar, termasuk judul dan tombol kembali.

Yang akan Anda build

  • Anda akan mengimplementasikan navigasi di aplikasi multilayar.

Yang Anda butuhkan

  • Versi terbaru Android Studio
  • Koneksi internet untuk mendownload kode awal

2. 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-cupcake.git

$ cd basic-android-kotlin-compose-training-cupcake
$ git checkout starter

3. Panduan aplikasi

Aplikasi Cupcake sedikit berbeda dari aplikasi yang telah Anda tangani sejauh ini. Aplikasi memiliki empat layar terpisah, bukan menampilkan semua konten di satu layar. Selain itu, pengguna dapat menjelajahi setiap layar sambil memesan cupcake,

Layar untuk memulai pesanan

Layar pertama menampilkan tiga tombol kepada pengguna yang sesuai dengan jumlah cupcake yang dapat dipesan.

Layar pertama aplikasi Cupcake dengan opsi untuk memulai pesanan satu, enam, atau dua belas cupcake.

Dalam kode, ini diwakili oleh composable StartOrderScreen di StartOrderScreen.kt.

Layar terdiri dari satu kolom, dengan gambar dan teks, beserta tiga tombol khusus untuk memesan cupcake dalam jumlah berbeda. Tombol kustom diimplementasikan oleh composable SelectQuantityButton, yang juga ada di StartOrderScreen.kt.

Layar untuk memilih rasa

Setelah memilih jumlah, aplikasi akan meminta pengguna untuk memilih rasa cupcake. Aplikasi menggunakan tombol pilihan untuk menampilkan berbagai opsi. Pengguna dapat memilih satu rasa dari pilihan kemungkinan rasa.

Aplikasi Cupcake memberikan pilihan rasa yang berbeda kepada pengguna.

Daftar kemungkinan rasa disimpan sebagai daftar ID resource string di data.DataSource.kt.

Layar untuk memilih tanggal pengambilan

Setelah memilih rasa, aplikasi menampilkan serangkaian tombol pilihan lain kepada pengguna untuk memilih tanggal pengambilan. Opsi pengambilan berasal dari daftar yang ditampilkan oleh fungsi pickupOptions() di OrderViewModel.

Aplikasi Cupcake yang menampilkan pilihan tanggal pengambilan kepada pengguna.

Layar Pilih Rasa dan layar Pilih Tanggal Pengambilan diwakili oleh composable yang sama, SelectOptionScreen di SelectOptionScreen.kt. Mengapa menggunakan composable yang sama? Tata letak layar ini sama persis. Satu-satunya perbedaan adalah data, tetapi Anda dapat menggunakan composable yang sama untuk menampilkan layar untuk memilih rasa dan tanggal pengambilan.

Layar Ringkasan Pesanan

Setelah memilih tanggal pengambilan, aplikasi akan menampilkan layar Ringkasan Pesanan tempat pengguna dapat meninjau dan menyelesaikan pesanan.

Aplikasi cupcake menyajikan ringkasan pesanan termasuk jumlah, rasa, tanggal pengambilan, dan subtotal selain opsi untuk mengirim pesanan ke aplikasi lain atau membatalkan pesanan.

Layar ini diimplementasikan oleh composable OrderSummaryScreen di OrderSummaryScreen.kt.

Tata letak terdiri dari Column yang berisi semua informasi tentang pesanannya, composable Text untuk subtotal, dan tombol untuk mengirimkan pesanan ke aplikasi lain atau membatalkan pesanan dan kembali ke layar pertama.

Jika pengguna memilih untuk mengirimkan pesanan ke aplikasi lain, aplikasi Cupcake akan menampilkan sheet bawah yang menampilkan opsi berbagi yang berbeda.

Aplikasi Cupcake menampilkan opsi berbagi kepada pengguna seperti SMS atau Email.

Status aplikasi saat ini disimpan di data.OrderUiState.kt. Class data OrderUiState berisi properti untuk menyimpan pilihan pengguna dari setiap layar.

Layar aplikasi akan ditampilkan dalam composable CupcakeApp. Namun, dalam project permulaan, aplikasi hanya menampilkan layar pertama. Saat ini, Anda tidak dapat membuka semua layar aplikasi, tetapi jangan khawatir, untuk itulah Anda ada di sini. Anda akan mempelajari cara menentukan rute navigasi, menyiapkan composable NavHost untuk melakukan navigasi antarlayar—yang juga dikenal sebagai tujuan—melakukan intent untuk berintegrasi dengan komponen UI sistem seperti layar berbagi, dan membuat AppBar merespons perubahan navigasi.

Composable yang dapat digunakan kembali

Jika sesuai, aplikasi contoh dalam kursus ini dirancang untuk menerapkan praktik terbaik. Begitu pula dengan aplikasi Cupcake. Dalam paket ui.components, Anda akan melihat file bernama CommonUi.kt yang berisi composable FormattedPriceLabel. Beberapa layar di aplikasi menggunakan composable ini untuk memformat harga pesanan secara konsisten. Daripada membuat duplikat composable Text yang sama dengan format dan pengubah yang sama, Anda dapat menentukan FormattedPriceLabel satu kali, lalu menggunakannya kembali sebanyak yang diperlukan untuk layar lainnya.

Layar rasa dan tanggal pengambilan menggunakan composable SelectOptionScreen, yang juga dapat digunakan kembali. Composable ini mengambil parameter bernama options dari jenis List<String> yang mewakili opsi untuk ditampilkan. Opsi muncul di Row, yang terdiri dari composable RadioButton dan composable Text yang berisi setiap string. Column mengelilingi seluruh tata letak dan juga berisi composable Text untuk menampilkan harga berformat, tombol Cancel, dan tombol Next.

4. Menentukan rute dan membuat NavHostController

Bagian dari Komponen Navigasi

Komponen Navigasi memiliki tiga bagian utama:

  • NavController: Bertanggung jawab untuk menavigasi di antara tujuan—yaitu layar di aplikasi Anda.
  • NavGraph: Memetakan tujuan composable untuk dinavigasi.
  • NavHost: Composable yang bertindak sebagai container untuk menampilkan tujuan NavGraph saat ini.

Dalam codelab ini, Anda akan berfokus pada NavController dan NavHost. Dalam NavHost, Anda akan menentukan tujuan untuk NavGraph aplikasi Cupcake.

Menentukan rute untuk tujuan di aplikasi Anda

Salah satu konsep dasar navigasi di aplikasi Compose adalah rute. Rute adalah string yang sesuai dengan tujuan. Ide ini mirip dengan konsep URL. Sama seperti URL berbeda yang dipetakan ke halaman yang berbeda di situs, rute adalah string yang dipetakan ke tujuan dan berfungsi sebagai ID uniknya. Tujuan biasanya berupa Composable tunggal atau grup Composable yang sesuai dengan apa yang dilihat pengguna. Aplikasi Cupcake memerlukan tujuan untuk layar mulai pesanan, layar rasa, layar tanggal pengambilan, dan layar ringkasan pesanan.

Aplikasi memiliki jumlah layar yang terbatas, sehingga rute juga terbatas. Anda dapat menentukan rute aplikasi menggunakan class enum. Class Enum di Kotlin memiliki properti nama yang menampilkan string dengan nama properti.

Anda akan mulai dengan menentukan empat rute aplikasi Cupcake.

  • Start: Pilih jumlah cupcake dari salah satu dari tiga tombol.
  • Flavor: Pilih rasa dari daftar pilihan.
  • Pickup: Pilih tanggal pengambilan dari daftar pilihan.
  • Summary: Tinjau pilihan, lalu kirim atau batalkan pesanan.

Tambahkan class enum untuk menentukan rute.

  1. Di CupcakeScreen.kt, di atas composable CupcakeAppBar, tambahkan class enum bernama CupcakeScreen.
enum class CupcakeScreen() {

}
  1. Tambahkan empat kasus ke class enum: Start, Flavor, Pickup, dan Summary.
enum class CupcakeScreen() {
    Start,
    Flavor,
    Pickup,
    Summary
}

Menambahkan NavHost ke aplikasi Anda

NavHost adalah Composable yang menampilkan tujuan composable lainnya, berdasarkan rute tertentu. Misalnya, jika rutenya adalah Flavor, NavHost akan menampilkan layar untuk memilih rasa cupcake. Jika rutenya adalah Summary, aplikasi akan menampilkan layar ringkasan.

Sintaksis untuk NavHost sama seperti Composable lainnya.

fae7688d6dd53de9.png

Ada dua parameter penting.

  • navController: Instance dari class NavHostController. Anda dapat menggunakan objek ini untuk berpindah antarlayar, misalnya, dengan memanggil metode navigate() untuk menuju ke tujuan lain. Anda dapat memperoleh NavHostController dengan memanggil rememberNavController() dari fungsi composable.
  • startDestination: Rute string yang menentukan tujuan yang ditampilkan secara default saat aplikasi pertama kali menampilkan NavHost. Untuk aplikasi Cupcake, seharusnya ini adalah rute Start.

Seperti composable lainnya, NavHost juga menggunakan parameter modifier.

Anda akan menambahkan NavHost ke composable CupcakeApp di CupcakeScreen.kt. Pertama, Anda memerlukan referensi untuk pengontrol navigasi. Anda dapat menggunakan pengontrol navigasi di NavHost yang Anda tambahkan sekarang dan AppBar yang akan Anda tambahkan pada langkah berikutnya. Oleh karena itu, Anda harus mendeklarasikan variabel dalam composable CupcakeApp().

  1. Buka CupcakeScreen.kt.
  2. Di atas variabel viewModel dalam composable CupcakeApp, buat variabel baru menggunakan val bernama navController dan setel ke nilai yang sama dengan hasil pemanggilan rememberNavController().
@Composable
fun CupcakeApp(modifier: Modifier = Modifier){
    val navController = rememberNavController()

    ...
}
  1. Dalam Scaffold, di bawah variabel uiState, tambahkan composable NavHost.
Scaffold(
    ...
) { innerPadding ->
    val uiState by viewModel.uiState.collectAsState()

    NavHost()
}
  1. Teruskan variabel navController untuk parameter navController dan CupcakeScreen.Start.name untuk parameter startDestination. Teruskan pengubah yang diteruskan ke CupcakeApp() untuk parameter pengubah. Teruskan lambda akhir kosong untuk parameter akhir.
NavHost(
   navController = navController,
   startDestination = CupcakeScreen.Start.name,
   modifier = modifier.padding(innerPadding)
) {
}

Menangani rute di NavHost Anda

Seperti composable lainnya, NavHost menggunakan jenis fungsi untuk kontennya.

f67974b7fb3f0377.png

Dalam fungsi konten NavHost, Anda memanggil fungsi composable(). Fungsi composable() memerlukan dua parameter.

  • route: String yang sesuai dengan nama rute. Ini dapat berupa string unik apa pun. Anda akan menggunakan properti nama konstanta enum CupcakeScreen.
  • content: Di sini Anda dapat memanggil composable yang ingin ditampilkan untuk rute yang diberikan.

Anda akan memanggil fungsi composable() satu kali untuk masing-masing dari keempat rute.

  1. Panggil fungsi composable(), dengan meneruskan CupcakeScreen.Start.name untuk route.
NavHost(
   navController = navController,
   startDestination = CupcakeScreen.Start.name,
   modifier = modifier.padding(innerPadding)
) {
    composable(route = CupcakeScreen.Start.name) {

    }
}
  1. Dalam lambda terakhir, panggil composable StartOrderScreen, dengan meneruskan quantityOptions untuk properti quantityOptions.
NavHost(
   navController = navController,
   startDestination = CupcakeScreen.Start.name,
   modifier = modifier.padding(innerPadding)
) {
    composable(route = CupcakeScreen.Start.name) {
        StartOrderScreen(
            quantityOptions = quantityOptions
        )
    }
}
  1. Di bawah panggilan pertama ke composable(), panggil composable() lagi, dengan meneruskan CupcakeScreen.Flavor.name untuk route.
composable(route = CupcakeScreen.Flavor.name) {

}
  1. Dalam lambda terakhir, dapatkan referensi ke LocalContext.current dan simpan dalam variabel bernama context. Anda dapat menggunakan variabel ini untuk mendapatkan string dari daftar ID resource dalam model tampilan untuk menampilkan daftar rasa.
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current

}
  1. Panggil composable SelectOptionScreen.
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(

    )
}
  1. Layar rasa perlu menampilkan dan mengupdate subtotal saat pengguna memilih rasa. Teruskan uiState.price untuk parameter subtotal.
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(
        subtotal = uiState.price
    )
}
  1. Layar rasa mendapatkan daftar rasa dari resource string aplikasi. Buat daftar string dari daftar rasa dalam model tampilan. Anda dapat mengubah daftar ID resource menjadi daftar string menggunakan fungsi map() dan memanggil stringResource().
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(
        subtotal = uiState.price,
        options = flavors.map { id -> stringResource(id) }
    )
}
  1. Untuk parameter onSelectionChanged, teruskan ekspresi lambda yang memanggil setFlavor() pada model tampilan, dengan meneruskan it (argumen diteruskan ke onSelectionChanged()).
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(
        subtotal = uiState.price,
        options = flavors.map { id -> context.resources.getString(id) },
        onSelectionChanged = { viewModel.setFlavor(it) }
    )
}

Layar tanggal pengambilan mirip dengan layar rasa. Satu-satunya perbedaan adalah data yang diteruskan ke composable SelectOptionScreen.

  1. Panggil lagi fungsi composable() dengan meneruskan CupcakeScreen.Pickup.name untuk parameter route.
composable(route = CupcakeScreen.Pickup.name) {

}
  1. Di lambda terakhir, panggil composable SelectOptionScreen dan teruskan uiState.price untuk subtotal, seperti sebelumnya. Teruskan uiState.pickupOptions untuk parameter options dan ekspresi lambda yang memanggil setDate() di viewModel untuk parameter onSelectionChanged.
SelectOptionScreen(
    subtotal = uiState.price,
    options = uiState.pickupOptions,
    onSelectionChanged = { viewModel.setDate(it) }
)
  1. Panggil composable() sekali lagi, dengan meneruskan CupcakeScreen.Summary.name untuk route.
composable(route = CupcakeScreen.Summary.name) {

}
  1. Di lambda terakhir, panggil composable OrderSummaryScreen(), dengan meneruskan variabel uiState untuk parameter orderUiState.
composable(route = CupcakeScreen.Summary.name) {
    OrderSummaryScreen(
        orderUiState = uiState
    )
}

Seperti itulah cara untuk menyiapkan NavHost. Di bagian berikutnya, Anda akan membuat aplikasi mengubah rute dan menavigasi antarlayar saat pengguna mengetuk setiap tombol.

5. Menavigasi antara rute

Setelah Anda menentukan rute dan memetakannya ke composable di NavHost, kini saatnya menavigasi antarlayar. NavHostController, yang merupakan properti navController dari panggilan rememberNavController(), bertanggung jawab untuk navigasi di antara rute. Namun, perhatikan bahwa properti ini ditentukan dalam composable CupcakeApp. Anda memerlukan cara untuk mengaksesnya dari berbagai layar di aplikasi Anda.

Mudah, kan? Cukup teruskan navController sebagai parameter ke setiap composable.

Meskipun pendekatan ini berhasil, ini bukan cara yang ideal untuk merancang aplikasi. Manfaat menggunakan NavHost untuk menangani navigasi aplikasi Anda adalah logika navigasi disimpan terpisah dari masing-masing UI. Opsi ini menghindari beberapa kelemahan utama dalam meneruskan navController sebagai parameter.

  • Logika navigasi disimpan di satu tempat, yang bisa membuat kode Anda lebih mudah dikelola dan mencegah bug dengan tidak sengaja memberikan kontrol navigasi bebas pada setiap layar di aplikasi.
  • Di aplikasi yang perlu berfungsi pada berbagai faktor bentuk (seperti ponsel mode potret, ponsel foldable, atau tablet layar besar), tombol mungkin atau mungkin tidak memicu navigasi, bergantung pada tata letak aplikasi. Masing-masing layar harus bersifat mandiri dan tidak perlu mengetahui layar lain di aplikasi.

Sebagai gantinya, pendekatan kita adalah meneruskan jenis fungsi ke setiap composable untuk apa yang akan terjadi saat pengguna mengklik tombol. Dengan demikian, composable dan setiap composable turunannya memutuskan kapan harus memanggil fungsi. Namun, logika navigasi tidak ditampilkan ke setiap layar di aplikasi Anda. Semua perilaku navigasi ditangani di NavHost.

Menambahkan pengendali tombol ke StartOrderScreen

Anda akan memulai dengan menambahkan parameter jenis fungsi yang dipanggil saat salah satu tombol kuantitas ditekan di layar pertama. Fungsi ini diteruskan ke dalam composable StartOrderScreen dan bertanggung jawab untuk memperbarui model tampilan dan membuka layar berikutnya.

  1. Buka StartOrderScreen.kt.
  2. Di bawah parameter quantityOptions, dan sebelum parameter pengubah, tambahkan parameter bernama onNextButtonClicked dari jenis () -> Unit.
@Composable
fun StartOrderScreen(
    quantityOptions: List<Pair<Int, Int>>,
    onNextButtonClicked: () -> Unit,
    modifier: Modifier = Modifier
){
...
}

Setiap tombol sesuai dengan jumlah cupcake yang berbeda. Anda akan memerlukan informasi ini agar fungsi yang diteruskan untuk onNextButtonClicked dapat mengupdate model tampilan.

  1. Ubah jenis parameter onNextButtonClicked untuk mengambil parameter Int.
onNextButtonClicked: (Int) -> Unit,

Agar Int diteruskan saat memanggil onNextButtonClicked(), lihat jenis parameter quantityOptions.

Jenisnya adalah List<Pair<Int, Int>> atau daftar Pair<Int, Int>. Anda mungkin tidak mengenal jenis Pair, tetapi sama seperti namanya, ini adalah sepasang nilai. Pair menggunakan dua parameter jenis generik. Dalam hal ini, keduanya adalah jenis Int.

8326701a77706258.png

Setiap item dalam pasangan diakses oleh properti pertama atau properti kedua. Untuk parameter quantityOptions composable StartOrderScreen, Int pertama adalah ID resource untuk string yang ditampilkan pada setiap tombol. Int kedua adalah jumlah cupcake sebenarnya.

Kita akan meneruskan properti kedua dari pasangan yang dipilih saat memanggil fungsi onNextButtonClicked().

  1. Teruskan ekspresi lambda untuk parameter onClick dari SelectQuantityButton.
quantityOptions.forEach { item ->
    SelectQuantityButton(
        labelResourceId = item.first,
        onClick = {  }
    )
}
  1. Dalam ekspresi lambda, panggil onNextButtonClicked dengan meneruskan item.second, yaitu jumlah cupcake.
quantityOptions.forEach { item ->
    SelectQuantityButton(
        labelResourceId = item.first,
        onClick = { onNextButtonClicked(item.second) }
    )
}

Menambahkan pengendali tombol ke SelectOptionScreen

  1. Di bawah parameter onSelectionChanged composable SelectOptionScreen di SelectOptionScreen.kt, tambahkan parameter bernama onCancelButtonClicked dari jenis () -> Unit.
@Composable
fun SelectOptionScreen(
    subtotal: String,
    options: List<String>,
    onSelectionChanged: (String) -> Unit = {},
    onCancelButtonClicked: () -> Unit = {},
    modifier: Modifier = Modifier
)
  1. Di bawah parameter onCancelButtonClicked, tambahkan parameter lain dari jenis () -> Unit bernama onNextButtonClicked.
@Composable
fun SelectOptionScreen(
    subtotal: String,
    options: List<String>,
    onSelectionChanged: (String) -> Unit = {},
    onCancelButtonClicked: () -> Unit = {},
    onNextButtonClicked: () -> Unit = {},
    modifier: Modifier = Modifier
)
  1. Teruskan onCancelButtonClicked untuk parameter onClick tombol batal.
OutlinedButton(modifier = Modifier.weight(1f), onClick = onCancelButtonClicked) {
    Text(stringResource(R.string.cancel))
}
  1. Teruskan onNextButtonClicked untuk parameter onClick tombol berikutnya.
Button(
    modifier = Modifier.weight(1f),
    enabled = selectedValue.isNotEmpty(),
    onClick = onNextButtonClicked
) {
    Text(stringResource(R.string.next))
}

Menambahkan pengendali tombol ke SummaryScreen

Terakhir, tambahkan fungsi pengendali tombol untuk tombol Cancel dan Send pada layar ringkasan.

  1. Pada composable OrderSummaryScreen di OrderSummaryScreen.kt, tambahkan parameter bernama onCancelButtonClicked dari jenis () -> Unit.
@Composable
fun OrderSummaryScreen(
    orderUiState: OrderUiState,
    onCancelButtonClicked: () -> Unit,
    modifier: Modifier = Modifier
){
    ...
}
  1. Tambahkan parameter lain dari jenis () -> Unit dan beri nama onSendButtonClicked.
@Composable
fun OrderSummaryScreen(
    orderUiState: OrderUiState,
    onCancelButtonClicked: () -> Unit,
    onSendButtonClicked: (String, String) -> Unit,
    modifier: Modifier = Modifier
){
    ...
}
  1. Teruskan onSendButtonClicked untuk parameter onClick dari tombol Send. Teruskan newOrder dan orderSummary, dua variabel yang ditentukan sebelumnya di OrderSummaryScreen. String ini terdiri dari data aktual yang dapat dibagikan pengguna kepada aplikasi lain.
Button(
    modifier = Modifier.fillMaxWidth(),
    onClick = { onSendButtonClicked(newOrder, orderSummary) }
) {
    Text(stringResource(R.string.send))
}
  1. Teruskan onCancelButtonClicked untuk parameter onClick pada tombol Cancel.
OutlinedButton(
    modifier = Modifier.fillMaxWidth(),
    onClick = onCancelButtonClicked
) {
    Text(stringResource(R.string.cancel))
}

Untuk membuka rute lain, cukup panggil metode navigate() pada instance NavHostController Anda.

fc8aae3911a6a25d.png

Metode navigasi mengambil satu parameter: string yang sesuai dengan rute yang ditentukan di NavHost. Jika rute cocok dengan salah satu panggilan ke composable() di NavHost, aplikasi akan membuka layar tersebut.

Anda akan meneruskan fungsi yang memanggil navigate() saat pengguna menekan tombol pada layar Start, Flavor, dan Pickup.

  1. Di CupcakeScreen.kt, temukan panggilan ke composable() untuk layar mulai. Untuk parameter onNextButtonClicked, teruskan ekspresi lambda.
StartOrderScreen(
    quantityOptions = quantityOptions,
    onNextButtonClicked = {
    }
)

Ingat properti Int yang diteruskan ke fungsi ini untuk jumlah cupcake? Sebelum membuka layar berikutnya, Anda harus mengupdate model tampilan agar aplikasi menampilkan subtotal yang benar.

  1. Panggil setQuantity pada viewModel, yang meneruskan it.
onNextButtonClicked = {
    viewModel.setQuantity(it)
}
  1. Panggil navigate() pada navController, yang meneruskan CupcakeScreen.Flavor.name untuk route.
onNextButtonClicked = {
    viewModel.setQuantity(it)
    navController.navigate(CupcakeScreen.Flavor.name)
}
  1. Untuk parameter onNextButtonClicked di layar rasa, cukup teruskan lambda yang memanggil navigate(), dengan meneruskan CupcakeScreen.Pickup.name untuk route.
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(
        subtotal = uiState.price,
        onNextButtonClicked = {
            navController.navigate(CupcakeScreen.Pickup.name) },
        options = flavors.map { id -> context.resources.getString(id) },
        onSelectionChanged = { viewModel.setFlavor(it) }
    )
}
  1. Teruskan lambda kosong untuk onCancelButtonClicked yang akan Anda implementasikan berikutnya.
SelectOptionScreen(
     subtotal = uiState.price,
    onNextButtonClicked = {
        navController.navigate(CupcakeScreen.Pickup.name) },
    onCancelButtonClicked = {},
    options = flavors.map { id -> context.resources.getString(id) },
    onSelectionChanged = { viewModel.setFlavor(it) }
)
  1. Untuk parameter onNextButtonClicked di layar pengambilan, teruskan lambda yang memanggil navigate(), dengan meneruskan CupcakeScreen.Summary.name untuk route.
composable(route = CupcakeScreen.Pickup.name) {
    SelectOptionScreen(
        subtotal = uiState.price,
        onNextButtonClicked = {
            navController.navigate(CupcakeScreen.Summary.name)
        },
        options = uiState.pickupOptions,
        onSelectionChanged = { viewModel.setDate(it) }
    )
}
  1. Sekali lagi, teruskan lambda kosong untuk onCancelButtonClicked().
SelectOptionScreen(
    subtotal = uiState.price,
    onNextButtonClicked = {
        navController.navigate(CupcakeScreen.Summary.name) },
    onCancelButtonClicked = {},
    options = uiState.pickupOptions,
    onSelectionChanged = { viewModel.setDate(it) }
)
  1. Untuk OrderSummaryScreen, teruskan lambda kosong untuk onCancelButtonClicked dan onSendButtonClicked. Tambahkan parameter untuk subject dan summary yang diteruskan ke onSendButtonClicked, yang akan segera Anda implementasikan.
composable(route = CupcakeScreen.Summary.name) {
   val context = LocalContext.current
   OrderSummaryScreen(
       orderUiState = uiState,
       onCancelButtonClicked = {},
       onSendButtonClicked = { subject: String, summary: String ->

       }
   )
}

Sekarang Anda dapat membuka setiap layar aplikasi. Perhatikan bahwa dengan memanggil navigate(), layar tidak hanya berubah, tetapi juga sebenarnya ditempatkan di atas data sebelumnya. Selain itu, saat menekan tombol kembali sistem, Anda dapat kembali ke layar sebelumnya.

Aplikasi menumpuk setiap layar di atas layar sebelumnya, dan tombol kembali (bade5f3ecb71e4a2.png) dapat menghapusnya. Histori layar dari startDestination di bagian bawah hingga layar teratas yang baru saja ditampilkan dikenal sebagai data sebelumnya.

Membuka layar awal

Tidak seperti tombol kembali sistem, tombol Cancel tidak kembali ke layar sebelumnya. Sebagai gantinya, aplikasi akan menghilangkan—menghapus—semua layar dari data sebelumnya dan kembali ke layar awal.

Anda dapat melakukannya dengan memanggil metode popBackStack().

2f382e5eb319b4b8.png

Metode popBackStack() memerlukan dua parameter.

  • route: String yang mewakili rute tujuan yang akan dinavigasikan kembali.
  • inclusive: Nilai Boolean yang, jika benar, juga akan menampilkan (menghapus) rute yang ditentukan. Jika salah, popBackStack() akan menghapus semua tujuan di atas—tetapi tidak termasuk—tujuan awal, sehingga membiarkannya sebagai layar teratas yang terlihat oleh pengguna.

Saat pengguna menekan tombol Cancel pada layar mana pun, aplikasi akan mereset status dalam model tampilan dan memanggil popBackStack(). Anda akan menerapkan metode untuk melakukan ini terlebih dahulu, lalu meneruskannya untuk parameter yang sesuai di ketiga layar dengan tombol Cancel.

  1. Setelah fungsi CupcakeApp(), tentukan fungsi pribadi yang disebut cancelOrderAndNavigateToStart().
private fun cancelOrderAndNavigateToStart() {
}
  1. Tambahkan dua parameter: viewModel dari jenis OrderViewModel, dan navController dari jenis NavHostController.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
}
  1. Di isi fungsi, panggil resetOrder() pada viewModel.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
    viewModel.resetOrder()
}
  1. Panggil popBackStack() pada navController, yang meneruskan CupcakeScreen.Start.name untuk route, dan false untuk inclusive.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
    viewModel.resetOrder()
    navController.popBackStack(CupcakeScreen.Start.name, inclusive = false)
}
  1. Di composable CupcakeApp(), teruskan cancelOrderAndNavigateToStart untuk parameter onCancelButtonClicked dari dua composable SelectOptionScreen dan composable OrderSummaryScreen.
composable(route = CupcakeScreen.Start.name) {
   StartOrderScreen(
       quantityOptions = quantityOptions,
       onNextButtonClicked = {
           viewModel.setQuantity(it)
           navController.navigate(CupcakeScreen.Flavor.name)
       }
   )
}
composable(route = CupcakeScreen.Flavor.name) {
   val context = LocalContext.current
   SelectOptionScreen(
       subtotal = uiState.price,
       onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
       onCancelButtonClicked = {
           cancelOrderAndNavigateToStart(viewModel, navController)
       },
       options = flavors.map { id -> context.resources.getString(id) },
       onSelectionChanged = { viewModel.setFlavor(it) }
   )
}
  1. Jalankan aplikasi Anda dan uji apakah menekan tombol Cancel di layar mana pun akan mengarahkan pengguna kembali ke layar pertama.

6. Membuka aplikasi lain

Sejauh ini, Anda telah mempelajari cara membuka layar yang berbeda di aplikasi dan cara kembali ke layar root. Tinggal satu langkah lagi untuk menerapkan navigasi di aplikasi Cupcake. Pada layar ringkasan pesanan, pengguna dapat mengirimkan pesanannya ke aplikasi lain. Pilihan ini menampilkan sheet bawah—komponen antarmuka pengguna yang menutupi bagian bawah layar—yang menampilkan opsi berbagi.

UI ini bukan bagian dari aplikasi Cupcake. Bahkan, layanan ini disediakan oleh sistem operasi Android. UI sistem, seperti layar berbagi, tidak dipanggil oleh navController. Sebagai gantinya, Anda menggunakan Intent.

Intent adalah permintaan bagi sistem untuk melakukan beberapa tindakan, umumnya menghadirkan aktivitas baru. Ada banyak intent yang berbeda, dan sebaiknya Anda merujuk ke dokumentasi untuk mendapatkan daftar yang komprehensif. Namun, kita tertarik dengan yang disebut ACTION_SEND. Anda dapat memberi intent ini beberapa data, seperti string, dan menyajikan tindakan berbagi yang sesuai untuk data tersebut.

Proses dasar untuk menyiapkan intent adalah sebagai berikut:

  1. Buat objek intent dan tentukan intent tersebut, seperti ACTION_SEND.
  2. Tentukan jenis data tambahan yang dikirim dengan intent. Untuk teks sederhana, Anda dapat menggunakan "text/plain", meskipun jenis lain, seperti "image/*" atau "video/*", tersedia.
  3. Teruskan data tambahan ke intent, seperti teks atau gambar untuk dibagikan, dengan memanggil metode putExtra(). Intent ini akan memerlukan dua tambahan: EXTRA_SUBJECT dan EXTRA_TEXT.
  4. Panggil metode konteks startActivity(), dengan meneruskan aktivitas yang dibuat dari intent.

Kami akan memandu Anda dalam membuat intent tindakan berbagi, tetapi prosesnya sama untuk jenis intent lainnya. Untuk project selanjutnya, sebaiknya Anda membaca dokumentasi yang diperlukan untuk jenis data tertentu dan menambahkan yang diperlukan.

Selesaikan langkah-langkah berikut untuk membuat intent guna mengirim pesanan cupcake ke aplikasi lain:

  1. Di CupcakeScreen.kt, di bawah composable CupcakeApp, buat fungsi pribadi bernama shareOrder().
private fun shareOrder()
  1. Tambahkan parameter bernama context dari jenis Context.
private fun shareOrder(context: Context) {
}
  1. Tambahkan dua parameter String: subject dan summary. String ini akan ditampilkan di lembar tindakan berbagi.
private fun shareOrder(context: Context, subject: String, summary: String) {
}
  1. Dalam isi fungsi, buat Intent bernama intent, dan teruskan Intent.ACTION_SEND sebagai argumen.
val intent = Intent(Intent.ACTION_SEND)

Karena Anda hanya perlu mengonfigurasi objek Intent ini sekali, Anda dapat membuat beberapa baris kode berikutnya lebih ringkas menggunakan fungsi apply(), yang telah Anda pelajari di codelab sebelumnya.

  1. Panggil apply() pada Intent yang baru dibuat dan teruskan ekspresi lambda.
val intent = Intent(Intent.ACTION_SEND).apply {

}
  1. Di isi lambda, tetapkan jenis ke "text/plain". Karena Anda melakukan ini dalam fungsi yang diteruskan ke apply(), Anda tidak perlu merujuk ke ID objek, intent.
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
}
  1. Panggil putExtra(), dengan meneruskan subjek untuk EXTRA_SUBJECT.
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_SUBJECT, subject)
}
  1. Panggil putExtra(), dengan meneruskan ringkasan untuk EXTRA_TEXT.
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_SUBJECT, subject)
    putExtra(Intent.EXTRA_TEXT, summary)
}
  1. Panggil metode startActivity() konteks.
context.startActivity(

)
  1. Dalam lambda yang diteruskan ke startActivity(), buat aktivitas dari Intent dengan memanggil metode class createChooser(). Teruskan intent untuk argumen pertama dan resource string new_cupcake_order.
context.startActivity(
    Intent.createChooser(
        intent,
        context.getString(R.string.new_cupcake_order)
    )
)
  1. Pada composable CupcakeApp, dalam panggilan ke composable() untuk CucpakeScreen.Summary.name, dapatkan referensi ke objek konteks sehingga Anda dapat meneruskannya ke fungsi shareOrder().
composable(route = CupcakeScreen.Summary.name) {
    val context = LocalContext.current

    ...
}
  1. Dalam isi lambda onSendButtonClicked(), panggil shareOrder() dengan meneruskan context, subject, dan summary sebagai argumen.
onSendButtonClicked = { subject: String, summary: String ->
    shareOrder(context, subject = subject, summary = summary)
}
  1. Jalankan aplikasi Anda dan jelajahi layar.

Saat mengklik Send Order to Another App, Anda akan melihat tindakan berbagi seperti Messaging dan Bluetooth di sheet bawah, beserta subjek dan ringkasan yang Anda berikan sebagai tambahan.

Aplikasi Cupcake menampilkan opsi berbagi kepada pengguna seperti SMS atau Email.

7. Membuat AppBar merespons navigasi

Meskipun aplikasi Anda berfungsi dan dapat menavigasi ke dan dari setiap layar, masih ada sesuatu yang hilang dari screenshot di awal codelab ini. AppBar tidak otomatis merespons navigasi. Judul tidak diupdate saat aplikasi menuju ke rute baru atau menampilkan tombol Atas sebelum judul jika sesuai.

Kode awal menyertakan composable untuk mengelola AppBar yang bernama CupcakeAppBar. Setelah mengimplementasikan navigasi di aplikasi, Anda dapat menggunakan informasi dari data sebelumnya untuk menampilkan judul yang benar dan menampilkan tombol Atas jika sesuai.

Tombol Atas hanya muncul jika ada composable di data sebelumnya. Jika aplikasi tidak memiliki layar di data sebelumnya—StartOrderScreen ditampilkan—tombol Atas tidak akan ditampilkan. Untuk memeriksanya, Anda memerlukan referensi ke data sebelumnya.

  1. Pada composable CupcakeApp, di bawah variabel navController, buat variabel bernama backStackEntry dan panggil metode currentBackStackEntry() dari navController menggunakan delegasi by.
@Composable
fun CupcakeApp(modifier: Modifier = Modifier, viewModel: OrderViewModel = viewModel()){

    val navController = rememberNavController()

    val backStackEntry by navController.currentBackStackEntryAsState()

    ...
}
  1. Di CupcakeAppBar, teruskan backStackEntry?.destination?.route untuk parameter currentScreen. Karena ini bersifat nullable, gunakan operator elvis (?:) untuk menentukan CupcakeScreen.Start.name sebagai default.
currentScreen = backStackEntry?.destination?.route ?: CupcakeScreen.Start.name,

Selama ada layar di belakang layar saat ini pada data sebelumnya, tombol Atas akan muncul. Anda dapat menggunakan ekspresi boolean untuk mengidentifikasi apakah tombol Atas akan muncul.

  1. Untuk parameter canNavigateBack, teruskan ekspresi boolean yang memeriksa apakah properti previousBackStackEntry dari navController sama dengan null.
canNavigateBack = navController.previousBackStackEntry != null,
  1. Untuk benar-benar kembali ke layar sebelumnya, panggil metode navigateUp() navController.
navigateUp = { navController.navigateUp() }
  1. Jalankan aplikasi Anda.

Perhatikan bahwa judul AppBar sekarang diupdate untuk mencerminkan layar saat ini. Saat membuka layar selain StartOrderScreen, tombol Up akan muncul dan membawa Anda kembali ke layar sebelumnya.

Animasi yang menunjukkan pengguna membuka setiap layar di aplikasi Cupcake yang telah selesai.

8. Mendapatkan kode solusi

Guna mendownload kode untuk codelab yang sudah selesai, Anda dapat menggunakan perintah git ini:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git

$ cd basic-android-kotlin-compose-training-cupcake
$ git checkout navigation

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

Jika Anda ingin melihat kode solusi untuk codelab ini, lihat kode tersebut di GitHub.

9. Ringkasan

Selamat! Anda baru saja melakukan peningkatan dari aplikasi satu layar sederhana ke aplikasi multilayar yang kompleks menggunakan komponen Jetpack Navigation untuk berpindah di antara beberapa layar. Anda menentukan rute, menanganinya di NavHost, dan menggunakan parameter jenis fungsi untuk memisahkan logika navigasi dari setiap layar. Anda juga telah mempelajari cara mengirim data ke aplikasi lain menggunakan intent serta menyesuaikan panel aplikasi sebagai respons terhadap navigasi. Dalam unit mendatang, Anda akan terus menggunakan keterampilan ini saat mengerjakan beberapa aplikasi multilayar lainnya dengan kompleksitas yang semakin meningkat.

Mempelajari Lebih Lanjut