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 membangun aplikasi multilayar di Compose dengan mudah menggunakan pendekatan deklaratif, seperti membangun 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, sekaligus menunjukkan praktik terbaik dalam aplikasi yang makin 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. - Beralih 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
Jika Anda ingin melihat kode awal untuk codelab ini, lihat kode tersebut di GitHub.
3. Panduan aplikasi
Aplikasi Cupcake sedikit berbeda dari aplikasi yang telah Anda tangani sejauh ini. Aplikasi ini tidak menampilkan semua konten di satu layar, melainkan di empat layar terpisah. Selain itu, pengguna dapat menjelajahi setiap layar sambil memesan cupcake. Jika menjalankan aplikasi ini, Anda tidak akan melihat apa pun dan tidak akan dapat beralih antar-layar karena komponen navigasi belum ditambahkan ke kode aplikasi. Namun, Anda masih dapat memeriksa pratinjau composable untuk setiap layar dan mencocokkannya dengan layar akhir aplikasi di bawah.
Layar untuk memulai pesanan
Layar pertama menampilkan tiga tombol kepada pengguna yang sesuai dengan jumlah cupcake yang dapat dipesan.
Dalam kode, layar 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 beberapa 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 SummaryScreen.kt
.
Tata letak terdiri dari Column
yang berisi semua informasi pesanan pengguna, composable Text
untuk subtotal, dan tombol untuk mengirimkan pesanan ke aplikasi lain atau membatalkan pesanan dan kembali ke layar pertama.
Jika pengguna memilih untuk mengirim pesanan ke aplikasi lain, aplikasi Cupcake akan menampilkan Android ShareSheet yang menunjukkan opsi berbagi yang berbeda.
Status saat ini dari aplikasi 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
. - Dalam
Scaffold
, di bawah variabeluiState
, tambahkan composableNavHost
.
import androidx.navigation.compose.NavHost
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.
import androidx.compose.foundation.layout.padding
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
.
import androidx.navigation.compose.composable
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
. Untuk kartumodifier
diModifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium))
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.res.dimensionResource
import com.example.cupcake.ui.StartOrderScreen
import com.example.cupcake.data.DataSource
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
- 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
, lalu simpan dalam variabel bernamacontext
.Context
adalah class abstrak yang implementasinya disediakan oleh sistem Android. Class ini memungkinkan akses ke resource dan class khusus aplikasi, serta up-call untuk operasi tingkat aplikasi seperti aktivitas peluncuran, dll. Anda dapat menggunakan variabel ini untuk mendapatkan string dari daftar ID resource dalam model tampilan untuk menampilkan daftar rasa.
import androidx.compose.ui.platform.LocalContext
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. Ubah daftar ID resource menjadi daftar string menggunakan fungsi
map()
dan memanggilcontext.resources.getString(id)
untuk setiap rasa.
import com.example.cupcake.ui.SelectOptionScreen
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
options = DataSource.flavors.map { id -> context.resources.getString(id) }
)
}
- Untuk parameter
onSelectionChanged
, teruskan ekspresi lambda yang memanggilsetFlavor()
pada model tampilan, dengan meneruskanit
(argumen yang diteruskan keonSelectionChanged()
). Untuk parametermodifier
, teruskanModifier.fillMaxHeight().
import androidx.compose.foundation.layout.fillMaxHeight
import com.example.cupcake.data.DataSource.flavors
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
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
. Untuk parametermodifier
, teruskanModifier.fillMaxHeight().
SelectOptionScreen(
subtotal = uiState.price,
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
- 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
. Untuk parametermodifier
, teruskanModifier.fillMaxHeight().
import com.example.cupcake.ui.OrderSummaryScreen
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
modifier = Modifier.fillMaxHeight()
)
}
Seperti itulah cara 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
){
...
}
- Setelah composable
StartOrderScreen
mendapatkan nilai untukonNextButtonClicked
, cariStartOrderPreview
lalu teruskan isi lambda kosong ke parameteronNextButtonClicked
.
@Preview
@Composable
fun StartOrderPreview() {
CupcakeTheme {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {},
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
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 di setiap tombol. Int
kedua adalah jumlah cupcake sebenarnya.
Kita akan meneruskan properti kedua dari pasangan yang dipilih saat memanggil fungsi onNextButtonClicked()
.
- Temukan ekspresi lambda kosong 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
dengan nilai default{}
.
@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
dengan nilai default{}
.
@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
diSummaryScreen.kt
, tambahkan parameter bernamaonCancelButtonClicked
dari jenis() -> Unit
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
modifier: Modifier = Modifier
){
...
}
- Tambahkan parameter lain dari jenis
(String, String) -> Unit
lalu beri namaonSendButtonClicked
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
onSendButtonClicked: (String, String) -> Unit,
modifier: Modifier = Modifier
){
...
}
- Sekarang composable
OrderSummaryScreen
mendapatkan nilai untukonSendButtonClicked
danonCancelButtonClicked
. CariOrderSummaryPreview
, teruskan isi lambda kosong dengan dua parameterString
keonSendButtonClicked
dan isi lambda kosong ke parameteronCancelButtonClicked
.
@Preview
@Composable
fun OrderSummaryPreview() {
CupcakeTheme {
OrderSummaryScreen(
orderUiState = OrderUiState(0, "Test", "Test", "$300.00"),
onSendButtonClicked = { subject: String, summary: String -> },
onCancelButtonClicked = {},
modifier = Modifier.fillMaxHeight()
)
}
}
- 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 = DataSource.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 = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
- Teruskan lambda kosong untuk
onCancelButtonClicked
yang akan Anda implementasikan berikutnya.
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
onCancelButtonClicked = {},
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
- 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) },
modifier = Modifier.fillMaxHeight()
)
}
- 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) },
modifier = Modifier.fillMaxHeight()
)
- Untuk
OrderSummaryScreen
, teruskan lambda kosong untukonCancelButtonClicked
danonSendButtonClicked
. Tambahkan parameter untuksubject
dansummary
yang diteruskan keonSendButtonClicked
, yang akan segera Anda implementasikan.
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
onCancelButtonClicked = {},
onSendButtonClicked = { subject: String, summary: String ->
},
modifier = Modifier.fillMaxHeight()
)
}
Sekarang Anda dapat membuka setiap layar aplikasi. Perhatikan bahwa dengan memanggil navigate()
, layar tidak hanya berubah, tetapi juga 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, tombol ini 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 = DataSource.quantityOptions,
onNextButtonClicked = {
viewModel.setQuantity(it)
navController.navigate(CupcakeScreen.Flavor.name)
},
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
composable(route = CupcakeScreen.Pickup.name) {
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Summary.name) },
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
}
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
onSendButtonClicked = { subject: String, summary: String ->
},
modifier = Modifier.fillMaxHeight()
)
}
- 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 utama. Tinggal satu langkah lagi untuk menerapkan navigasi di aplikasi Cupcake. Pada layar ringkasan pesanan, pengguna dapat mengirimkan pesanannya ke aplikasi lain. Pilihan ini menampilkan ShareSheet—komponen antarmuka pengguna yang menutupi bagian bawah layar—yang menampilkan opsi berbagi.
UI ini bukan bagian dari aplikasi Cupcake, melainkan 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
.
import android.content.Context
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.
import android.content.Intent
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 panel aplikasi 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. Panel aplikasi tidak merespons navigasi secara otomatis. 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. Composable CupcakeAppBar
harus memperhatikan layar saat ini sehingga judul diupdate dengan tepat.
- Di enum
CupcakeScreen
di CupcakeScreen.kt, tambahkan parameter jenisInt
bernamatitle
menggunakan anotasi@StringRes
.
import androidx.annotation.StringRes
enum class CupcakeScreen(@StringRes val title: Int) {
Start,
Flavor,
Pickup,
Summary
}
- Tambahkan nilai resource untuk setiap kasus enum, yang sesuai dengan teks judul untuk setiap layar. Gunakan
app_name
untuk layarStart
,choose_flavor
untuk layarFlavor
,choose_pickup_date
untuk layarPickup
, danorder_summary
untuk layarSummary
.
enum class CupcakeScreen(@StringRes val title: Int) {
Start(title = R.string.app_name),
Flavor(title = R.string.choose_flavor),
Pickup(title = R.string.choose_pickup_date),
Summary(title = R.string.order_summary)
}
- Tambahkan parameter bernama
currentScreen
dari jenisCupcakeScreen
ke composableCupcakeAppBar
.
fun CupcakeAppBar(
currentScreen: CupcakeScreen,
canNavigateBack: Boolean,
navigateUp: () -> Unit = {},
modifier: Modifier = Modifier
)
- Di dalam
CupcakeAppBar
, ganti nama aplikasi hard code dengan judul layar saat ini dengan meneruskancurrentScreen.title
ke panggilan kestringResource()
untuk parameter judulTopAppBar
.
TopAppBar(
title = { Text(stringResource(currentScreen.title)) },
modifier = modifier,
navigationIcon = {
if (canNavigateBack) {
IconButton(onClick = navigateUp) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(R.string.back_button)
)
}
}
}
)
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 metodecurrentBackStackEntryAsState()
darinavController
menggunakan delegasiby
.
import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
fun CupcakeApp(
viewModel: OrderViewModel = viewModel(),
navController: NavHostController = rememberNavController()
){
val backStackEntry by navController.currentBackStackEntryAsState()
...
}
- Konversikan judul layar saat ini menjadi nilai
CupcakeScreen
. Di bawah variabelbackStackEntry
, buat variabel menggunakanval
bernamacurrentScreen
yang sama dengan hasil pemanggilan fungsi classvalueOf()
dariCupcakeScreen
, lalu teruskan rute tujuanbackStackEntry
. Gunakan operator elvis untuk memberikan nilai defaultCupcakeScreen.Start.name
.
val currentScreen = CupcakeScreen.valueOf(
backStackEntry?.destination?.route ?: CupcakeScreen.Start.name
)
- Teruskan nilai variabel
currentScreen
ke dalam parameter bernama sama dari composableCupcakeAppBar
.
CupcakeAppBar(
currentScreen = currentScreen,
canNavigateBack = false,
navigateUp = {}
)
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
tidak 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 Atas akan muncul dan membawa Anda kembali ke layar sebelumnya.
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-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.