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.
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
danColumn
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.
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.
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
.
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.
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.
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.
- Di
CupcakeScreen.kt
, di atas composableCupcakeAppBar
, tambahkan class enum bernamaCupcakeScreen
.
enum class CupcakeScreen() {
}
- Tambahkan empat kasus ke class enum:
Start
,Flavor
,Pickup
, danSummary
.
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.
Ada dua parameter penting.
navController
: Instance dari classNavHostController
. Anda dapat menggunakan objek ini untuk berpindah antarlayar, misalnya, dengan memanggil metodenavigate()
untuk menuju ke tujuan lain. Anda dapat memperolehNavHostController
dengan memanggilrememberNavController()
dari fungsi composable.startDestination
: Rute string yang menentukan tujuan yang ditampilkan secara default saat aplikasi pertama kali menampilkanNavHost
. Untuk aplikasi Cupcake, seharusnya ini adalah ruteStart
.
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()
.
- Buka
CupcakeScreen.kt
. - Di atas variabel
viewModel
dalam composableCupcakeApp
, buat variabel baru menggunakanval
bernamanavController
dan setel ke nilai yang sama dengan hasil pemanggilanrememberNavController()
.
@Composable
fun CupcakeApp(modifier: Modifier = Modifier){
val navController = rememberNavController()
...
}
- Dalam
Scaffold
, di bawah variabeluiState
, tambahkan composableNavHost
.
Scaffold(
...
) { innerPadding ->
val uiState by viewModel.uiState.collectAsState()
NavHost()
}
- Teruskan variabel
navController
untuk parameternavController
danCupcakeScreen.Start.name
untuk parameterstartDestination
. Teruskan pengubah yang diteruskan keCupcakeApp()
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.
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 enumCupcakeScreen
.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.
- Panggil fungsi
composable()
, dengan meneruskanCupcakeScreen.Start.name
untukroute
.
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
}
}
- Dalam lambda terakhir, panggil composable
StartOrderScreen
, dengan meneruskanquantityOptions
untuk propertiquantityOptions
.
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(
quantityOptions = quantityOptions
)
}
}
- Di bawah panggilan pertama ke
composable()
, panggilcomposable()
lagi, dengan meneruskanCupcakeScreen.Flavor.name
untukroute
.
composable(route = CupcakeScreen.Flavor.name) {
}
- Dalam lambda terakhir, dapatkan referensi ke
LocalContext.current
dan simpan dalam variabel bernamacontext
. 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
}
- Panggil composable
SelectOptionScreen
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
)
}
- Layar rasa perlu menampilkan dan mengupdate subtotal saat pengguna memilih rasa. Teruskan
uiState.price
untuk parametersubtotal
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price
)
}
- 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 memanggilstringResource()
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
options = flavors.map { id -> stringResource(id) }
)
}
- Untuk parameter
onSelectionChanged
, teruskan ekspresi lambda yang memanggilsetFlavor()
pada model tampilan, dengan meneruskanit
(argumen diteruskan keonSelectionChanged()
).
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
.
- Panggil lagi fungsi
composable()
dengan meneruskanCupcakeScreen.Pickup.name
untuk parameterroute
.
composable(route = CupcakeScreen.Pickup.name) {
}
- Di lambda terakhir, panggil composable
SelectOptionScreen
dan teruskanuiState.price
untuksubtotal
, seperti sebelumnya. TeruskanuiState.pickupOptions
untuk parameteroptions
dan ekspresi lambda yang memanggilsetDate()
diviewModel
untuk parameteronSelectionChanged
.
SelectOptionScreen(
subtotal = uiState.price,
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) }
)
- Panggil
composable()
sekali lagi, dengan meneruskanCupcakeScreen.Summary.name
untukroute
.
composable(route = CupcakeScreen.Summary.name) {
}
- Di lambda terakhir, panggil composable
OrderSummaryScreen()
, dengan meneruskan variabeluiState
untuk parameterorderUiState
.
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.
- Buka
StartOrderScreen.kt
. - Di bawah parameter
quantityOptions
, dan sebelum parameter pengubah, tambahkan parameter bernamaonNextButtonClicked
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.
- Ubah jenis parameter
onNextButtonClicked
untuk mengambil parameterInt
.
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
.
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()
.
- Teruskan ekspresi lambda untuk parameter
onClick
dariSelectQuantityButton
.
quantityOptions.forEach { item ->
SelectQuantityButton(
labelResourceId = item.first,
onClick = { }
)
}
- Dalam ekspresi lambda, panggil
onNextButtonClicked
dengan meneruskanitem.second
, yaitu jumlah cupcake.
quantityOptions.forEach { item ->
SelectQuantityButton(
labelResourceId = item.first,
onClick = { onNextButtonClicked(item.second) }
)
}
Menambahkan pengendali tombol ke SelectOptionScreen
- Di bawah parameter
onSelectionChanged
composableSelectOptionScreen
diSelectOptionScreen.kt
, tambahkan parameter bernamaonCancelButtonClicked
dari jenis() -> Unit
.
@Composable
fun SelectOptionScreen(
subtotal: String,
options: List<String>,
onSelectionChanged: (String) -> Unit = {},
onCancelButtonClicked: () -> Unit = {},
modifier: Modifier = Modifier
)
- Di bawah parameter
onCancelButtonClicked
, tambahkan parameter lain dari jenis() -> Unit
bernamaonNextButtonClicked
.
@Composable
fun SelectOptionScreen(
subtotal: String,
options: List<String>,
onSelectionChanged: (String) -> Unit = {},
onCancelButtonClicked: () -> Unit = {},
onNextButtonClicked: () -> Unit = {},
modifier: Modifier = Modifier
)
- Teruskan
onCancelButtonClicked
untuk parameteronClick
tombol batal.
OutlinedButton(modifier = Modifier.weight(1f), onClick = onCancelButtonClicked) {
Text(stringResource(R.string.cancel))
}
- Teruskan
onNextButtonClicked
untuk parameteronClick
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.
- Pada composable
OrderSummaryScreen
diOrderSummaryScreen.kt
, tambahkan parameter bernamaonCancelButtonClicked
dari jenis() -> Unit
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
modifier: Modifier = Modifier
){
...
}
- Tambahkan parameter lain dari jenis
() -> Unit
dan beri namaonSendButtonClicked
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
onSendButtonClicked: (String, String) -> Unit,
modifier: Modifier = Modifier
){
...
}
- Teruskan
onSendButtonClicked
untuk parameteronClick
dari tombol Send. TeruskannewOrder
danorderSummary
, dua variabel yang ditentukan sebelumnya diOrderSummaryScreen
. 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))
}
- Teruskan
onCancelButtonClicked
untuk parameteronClick
pada tombol Cancel.
OutlinedButton(
modifier = Modifier.fillMaxWidth(),
onClick = onCancelButtonClicked
) {
Text(stringResource(R.string.cancel))
}
Membuka rute lain
Untuk membuka rute lain, cukup panggil metode navigate()
pada instance NavHostController
Anda.
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
.
- Di
CupcakeScreen.kt
, temukan panggilan kecomposable()
untuk layar mulai. Untuk parameteronNextButtonClicked
, 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.
- Panggil
setQuantity
padaviewModel
, yang meneruskanit
.
onNextButtonClicked = {
viewModel.setQuantity(it)
}
- Panggil
navigate()
padanavController
, yang meneruskanCupcakeScreen.Flavor.name
untukroute
.
onNextButtonClicked = {
viewModel.setQuantity(it)
navController.navigate(CupcakeScreen.Flavor.name)
}
- Untuk parameter
onNextButtonClicked
di layar rasa, cukup teruskan lambda yang memanggilnavigate()
, dengan meneruskanCupcakeScreen.Pickup.name
untukroute
.
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) }
)
}
- 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) }
)
- Untuk parameter
onNextButtonClicked
di layar pengambilan, teruskan lambda yang memanggilnavigate()
, dengan meneruskanCupcakeScreen.Summary.name
untukroute
.
composable(route = CupcakeScreen.Pickup.name) {
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = {
navController.navigate(CupcakeScreen.Summary.name)
},
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) }
)
}
- 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) }
)
- Untuk
OrderSummaryScreen
, teruskan lambda kosong untukonCancelButtonClicked
danonSendButtonClicked
. Tambahkan parameter untuksubject
dansummary
yang diteruskan keonSendButtonClicked
, 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 () 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()
.
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.
- Setelah fungsi
CupcakeApp()
, tentukan fungsi pribadi yang disebutcancelOrderAndNavigateToStart()
.
private fun cancelOrderAndNavigateToStart() {
}
- Tambahkan dua parameter:
viewModel
dari jenisOrderViewModel
, dannavController
dari jenisNavHostController
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
}
- Di isi fungsi, panggil
resetOrder()
padaviewModel
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
viewModel.resetOrder()
}
- Panggil
popBackStack()
padanavController
, yang meneruskanCupcakeScreen.Start.name
untukroute
, danfalse
untukinclusive
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
viewModel.resetOrder()
navController.popBackStack(CupcakeScreen.Start.name, inclusive = false)
}
- Di composable
CupcakeApp()
, teruskancancelOrderAndNavigateToStart
untuk parameteronCancelButtonClicked
dari dua composableSelectOptionScreen
dan composableOrderSummaryScreen
.
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) }
)
}
- 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:
- Buat objek intent dan tentukan intent tersebut, seperti
ACTION_SEND
. - Tentukan jenis data tambahan yang dikirim dengan intent. Untuk teks sederhana, Anda dapat menggunakan
"text/plain"
, meskipun jenis lain, seperti"image/*"
atau"video/*"
, tersedia. - Teruskan data tambahan ke intent, seperti teks atau gambar untuk dibagikan, dengan memanggil metode
putExtra()
. Intent ini akan memerlukan dua tambahan:EXTRA_SUBJECT
danEXTRA_TEXT
. - 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:
- Di CupcakeScreen.kt, di bawah composable
CupcakeApp
, buat fungsi pribadi bernamashareOrder()
.
private fun shareOrder()
- Tambahkan parameter bernama
context
dari jenisContext
.
private fun shareOrder(context: Context) {
}
- Tambahkan dua parameter
String
:subject
dansummary
. String ini akan ditampilkan di lembar tindakan berbagi.
private fun shareOrder(context: Context, subject: String, summary: String) {
}
- Dalam isi fungsi, buat Intent bernama
intent
, dan teruskanIntent.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.
- Panggil
apply()
pada Intent yang baru dibuat dan teruskan ekspresi lambda.
val intent = Intent(Intent.ACTION_SEND).apply {
}
- Di isi lambda, tetapkan jenis ke
"text/plain"
. Karena Anda melakukan ini dalam fungsi yang diteruskan keapply()
, Anda tidak perlu merujuk ke ID objek,intent
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
}
- Panggil
putExtra()
, dengan meneruskan subjek untukEXTRA_SUBJECT
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, subject)
}
- Panggil
putExtra()
, dengan meneruskan ringkasan untukEXTRA_TEXT
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, subject)
putExtra(Intent.EXTRA_TEXT, summary)
}
- Panggil metode
startActivity()
konteks.
context.startActivity(
)
- Dalam lambda yang diteruskan ke
startActivity()
, buat aktivitas dari Intent dengan memanggil metode classcreateChooser()
. Teruskan intent untuk argumen pertama dan resource stringnew_cupcake_order
.
context.startActivity(
Intent.createChooser(
intent,
context.getString(R.string.new_cupcake_order)
)
)
- Pada composable
CupcakeApp
, dalam panggilan kecomposable()
untukCucpakeScreen.Summary.name
, dapatkan referensi ke objek konteks sehingga Anda dapat meneruskannya ke fungsishareOrder()
.
composable(route = CupcakeScreen.Summary.name) {
val context = LocalContext.current
...
}
- Dalam isi lambda
onSendButtonClicked()
, panggilshareOrder()
dengan meneruskancontext
,subject
, dansummary
sebagai argumen.
onSendButtonClicked = { subject: String, summary: String ->
shareOrder(context, subject = subject, summary = summary)
}
- 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.
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.
- Pada composable
CupcakeApp
, di bawah variabelnavController
, buat variabel bernamabackStackEntry
dan panggil metodecurrentBackStackEntry()
darinavController
menggunakan delegasiby
.
@Composable
fun CupcakeApp(modifier: Modifier = Modifier, viewModel: OrderViewModel = viewModel()){
val navController = rememberNavController()
val backStackEntry by navController.currentBackStackEntryAsState()
...
}
- Di
CupcakeAppBar
, teruskanbackStackEntry?.destination?.route
untuk parametercurrentScreen
. Karena ini bersifat nullable, gunakan operator elvis (?:
) untuk menentukanCupcakeScreen.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.
- Untuk parameter
canNavigateBack
, teruskan ekspresi boolean yang memeriksa apakah propertipreviousBackStackEntry
darinavController
sama dengan null.
canNavigateBack = navController.previousBackStackEntry != null,
- Untuk benar-benar kembali ke layar sebelumnya, panggil metode
navigateUp()
navController
.
navigateUp = { navController.navigateUp() }
- 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.
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.