1. Sebelum memulai
Anda telah mempelajari cara menggunakan aktivitas, fragmen, intent, data binding, komponen navigasi, dan dasar komponen arsitektur. Dalam codelab ini, Anda akan memadukan semuanya dan menggunakan sampel lanjutan, yaitu aplikasi pemesanan cupcake.
Anda akan mempelajari cara menggunakan ViewModel
bersama untuk berbagi data antarfragmen aktivitas yang sama dan konsep baru seperti transformasi LiveData
.
Prasyarat
- Terbiasa membaca dan memahami tata letak Android dalam XML
- Terbiasa dengan dasar-dasar Komponen Jetpack Navigation
- Dapat membuat grafik navigasi dengan tujuan fragmen di aplikasi
- Sebelumnya pernah menggunakan fragmen di dalam aktivitas
- Dapat membuat
ViewModel
untuk menyimpan data aplikasi - Dapat menggunakan data binding dengan
LiveData
agar UI selalu terbarui menurut data aplikasi diViewModel
Yang akan Anda pelajari
- Cara menerapkan praktik arsitektur aplikasi yang direkomendasikan di dalam kasus penggunaan yang lebih lanjut
- Cara menggunakan
ViewModel
bersama lintas fragmen dalam suatu aktivitas - Cara menerapkan transformasi
LiveData
Yang akan Anda buat
- Aplikasi Cupcake yang menampilkan alur pemesanan cupcake, yang memungkinkan pengguna memilih rasa, jumlah, dan tanggal pengambilan cupcake.
Yang akan Anda butuhkan
- Komputer yang dilengkapi Android Studio.
- Kode awal untuk aplikasi Cupcake.
2. Ringkasan aplikasi awal
Ringkasan aplikasi Cupcake
Aplikasi Cupcake menunjukkan cara mendesain dan menerapkan aplikasi pemesanan online. Di akhir tahap ini, Anda akan menyelesaikan aplikasi Cupcake dengan tampilan berikut. Pengguna dapat memilih jumlah, rasa, dan opsi lainnya untuk pemesanan cupcake.
Mendownload kode awal untuk codelab ini
Codelab ini menyediakan kode awal yang dapat Anda lanjutkan dengan membuat fitur yang diajarkan dalam codelab ini. Kode awal berisi kode yang tidak asing bagi Anda dari codelab sebelumnya.
Jika Anda mendownload kode awal dari GitHub, perhatikan bahwa nama folder project ini adalah android-basics-kotlin-cupcake-app-starter
. Pilih folder ini saat Anda membuka project di Android Studio.
Untuk mendapatkan kode codelab ini dan membukanya di Android Studio, lakukan hal berikut.
Mendapatkan kode
- Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
- Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.
- Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
- Temukan file di komputer Anda (mungkin di folder Downloads).
- Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.
Membuka project di Android Studio
- Mulai Android Studio.
- Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.
Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.
- Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
- Klik dua kali pada folder project tersebut.
- Tunggu Android Studio membuka project.
- Klik tombol Run untuk membangun dan menjalankan aplikasi. Pastikan aplikasi dibangun seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi disiapkan.
Panduan kode awal
- Buka project yang telah didownload di Android Studio. Nama folder project adalah
android-basics-kotlin-cupcake-app-starter
. Kemudian jalankan aplikasi. - Eksplorasi file untuk memahami kode awal. Untuk file tata letak, Anda dapat menggunakan opsi Split di sudut kanan atas untuk melihat pratinjau tata letak dan XML secara bersamaan.
- Saat mengompilasi dan menjalankan aplikasi, Anda akan melihat aplikasi tersebut tidak lengkap. Tombolnya tidak memiliki banyak fungsi (kecuali untuk menampilkan pesan
Toast
) dan Anda tidak dapat membuka fragmen lain.
Berikut ini adalah panduan file penting dalam project.
MainActivity:
MainActivity
memiliki kode yang mirip dengan kode default yang dihasilkan, yang menetapkan tampilan konten aktivitas sebagai activity_main.xml
. Kode ini menggunakan konstruktor AppCompatActivity(@LayoutRes int contentLayoutId)
berparameter, yang menerima tata letak yang akan di-inflate sebagai bagian dari super.onCreate(savedInstanceState)
.
Kode di class MainActivity
class MainActivity : AppCompatActivity(R.layout.activity_main)
sama dengan kode berikut yang menggunakan konstruktor AppCompatActivity
default:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Tata letak (folder res/layout):
Folder resource layout
berisi file aktivitas dan file tata letak fragmen. Ini adalah file tata letak sederhana, dan XML-nya mirip dengan codelab sebelumnya.
fragment_start.xml
adalah layar pertama yang ditampilkan di aplikasi. Layar ini memiliki gambar cupcake dan tiga tombol untuk memilih jumlah cupcake untuk dipesan: satu cupcake, enam cupcake, dan dua belas cupcake.fragment_flavor.xml
menampilkan daftar rasa cupcake sebagai opsi tombol pilihan dengan tombol Next.fragment_pickup.xml
memberikan opsi untuk memilih hari pengambilan dan tombol Next untuk membuka layar ringkasan.fragment_summary.xml
menampilkan ringkasan detail pesanan seperti jumlah, rasa, dan tombol untuk mengirim pesanan ke aplikasi lain.
Class fragmen:
StartFragment.kt
adalah layar pertama yang ditampilkan di aplikasi. Class ini berisi kode view binding dan pemroses klik untuk ketiga tombol.- Class
FlavorFragment.kt
,PickupFragment.kt
, danSummaryFragment.kt
sebagian besar berisi kode boilerplate dan pemroses klik untuk tombol Next atau Send Order to Another App, yang menampilkan pesan toast.
Resource (folder res):
- Folder
drawable
berisi aset cupcake untuk layar pertama, serta file ikon peluncur. navigation/nav_graph.xml
berisi empat tujuan fragmen (startFragment
,flavorFragment
,pickupFragment
, dansummaryFragment
) tanpa Actions, yang akan Anda tentukan nanti di codelab ini.- Folder
values
berisi warna, dimensi, string, gaya, dan tema yang digunakan untuk menyesuaikan tema aplikasi. Anda seharusnya sudah memahami jenis resource ini dari codelab sebelumnya.
3. Melengkapi Grafik Navigasi
Dalam tugas ini, Anda akan menghubungkan layar aplikasi Cupcake dan menyelesaikan penerapan navigasi yang tepat di dalam aplikasi.
Apakah Anda ingat apa yang kita perlukan untuk menggunakan komponen Navigasi? Ikuti panduan ini guna mengingat kembali tentang cara menyiapkan project dan aplikasi Anda untuk:
- Menyertakan library Jetpack Navigation
- Menambahkan
NavHost
ke aktivitas - Membuat grafik navigasi
- Menambahkan tujuan fragmen ke grafik navigasi
Menghubungkan tujuan di grafik navigasi
- Di Android Studio, di jendela Project, buka file res > navigation > nav_graph.xml. Beralihlah ke tab Design, jika belum dipilih.
- Tindakan ini akan membuka Navigation Editor untuk memvisualisasikan grafik navigasi di aplikasi. Anda akan melihat keempat fragmen yang sudah ada di aplikasi.
- Hubungkan tujuan fragmen di grafik navigasi. Buat tindakan dari
startFragment
keflavorFragment
, koneksi dariflavorFragment
kepickupFragment
, dan koneksi daripickupFragment
kesummaryFragment
. Ikuti langkah berikutnya jika Anda memerlukan petunjuk lebih detail. - Arahkan kursor ke startFragment sampai Anda melihat batas abu-abu di sekitar fragmen dan lingkaran abu-abu muncul di tengah tepi kanan fragmen. Klik lingkaran tersebut dan tarik ke flavorFragment, lalu lepaskan mouse.
- Tanda panah di antara kedua fragmen tersebut menunjukkan koneksi yang berhasil, yang berarti Anda akan dapat berpindah dari startFragment ke flavorFragment. Tindakan ini disebut tindakan Navigasi, yang telah Anda pelajari di codelab sebelumnya.
- Demikian pula, tambahkan tindakan navigasi dari flavorFragment ke pickupFragment dan dari pickupFragment ke summaryFragment. Setelah Anda selesai membuat tindakan navigasi, grafik navigasi lengkapnya harus tampak seperti berikut.
- Ketiga tindakan baru yang Anda buat ini juga akan muncul di panel Component Tree.
- Saat menentukan grafik navigasi, Anda juga ingin menentukan tujuan awal. Saat ini Anda dapat melihat bahwa startFragment memiliki ikon rumah kecil di sampingnya.
Itu menunjukkan bahwa startFragment akan menjadi fragmen pertama untuk ditampilkan di NavHost
. Tetapkan ini sebagai perilaku yang diinginkan aplikasi. Untuk referensi mendatang, Anda dapat mengubah tujuan awal kapan saja dengan mengklik kanan pada fragmen dan memilih opsi menu Set as Start Destination.
Berpindah dari fragmen awal ke fragmen rasa
Selanjutnya, Anda akan menambahkan kode untuk berpindah dari startFragment ke flavorFragment dengan mengetuk tombol di fragmen pertama, bukan menampilkan pesan Toast
. Di bawah ini adalah referensi tata letak fragmen awal. Anda akan meneruskan jumlah cupcake ke fragmen rasa di tugas berikutnya.
- Di jendela Project, buka file Kotlin di app > java > com.example.cupcake > StartFragment.
- Di metode
onViewCreated()
, perhatikan bahwa pemroses klik ditetapkan pada ketiga tombol. Saat setiap tombol diketuk, metodeorderCupcake()
dipanggil dengan jumlah cupcake (1, 6, atau 12 cupcake) sebagai parameternya.
Kode referensi:
orderOneCupcake.setOnClickListener { orderCupcake(1) }
orderSixCupcakes.setOnClickListener { orderCupcake(6) }
orderTwelveCupcakes.setOnClickListener { orderCupcake(12) }
- Di metode
orderCupcake()
, ganti kode yang menampilkan pesan toast dengan kode untuk membuka fragmen rasa. DapatkanNavController
menggunakan metodefindNavController()
dan panggilnavigate()
pada metode tersebut, yang meneruskan ID tindakan,R.id.action_startFragment_to_flavorFragment
. Pastikan ID tindakan ini cocok dengan tindakan yang dideklarasikan dinav_graph.xml.
Anda
Ganti
fun orderCupcake(quantity: Int) {
Toast.makeText(activity, "Ordered $quantity cupcake(s)", Toast.LENGTH_SHORT).show()
}
dengan
fun orderCupcake(quantity: Int) {
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Tambahkan Impor
import
androidx.navigation.fragment.findNavController
atau Anda dapat memilih dari opsi yang disediakan oleh Android Studio.
Menambahkan Navigasi ke fragmen rasa dan pengambilan
Mirip dengan tugas sebelumnya, dalam tugas ini Anda akan menambahkan navigasi ke fragmen lain: fragmen rasa dan pengambilan.
- Buka app > java > com.example.cupcake > FlavorFragment.kt. Perhatikan bahwa metode yang dipanggil di dalam pemroses klik tombol Next adalah metode
goToNextScreen()
. - Di
FlavorFragment.kt
, di dalam metodegoToNextScreen()
, ganti kode yang menampilkan toast untuk membuka fragmen pengambilan. Gunakan ID tindakan,R.id.action_flavorFragment_to_pickupFragment
, dan pastikan ID ini cocok dengan tindakan yang dideklarasikan dinav_graph.xml.
Anda
fun goToNextScreen() {
findNavController().navigate(R.id.action_flavorFragment_to_pickupFragment)
}
Jangan lupa lakukan import androidx.navigation.fragment.findNavController
.
- Demikian pula di
PickupFragment.kt
, di dalam metodegoToNextScreen()
, ganti kode yang ada untuk membuka fragmen ringkasan.
fun goToNextScreen() {
findNavController().navigate(R.id.action_pickupFragment_to_summaryFragment)
}
Impor androidx.navigation.fragment.findNavController
.
- Jalankan aplikasi. Pastikan tombolnya berfungsi untuk berpindah dari layar ke layar. Informasi yang ditampilkan pada setiap fragmen mungkin tidak lengkap, tetapi jangan khawatir, Anda akan mengisi fragmen tersebut dengan data yang benar di langkah berikutnya.
Memperbarui judul di panel aplikasi
Saat Anda menjelajahi aplikasi, perhatikan judul dalam panel aplikasi. Judul ini selalu ditampilkan sebagai Cupcake.
Untuk memperbaiki pengalaman pengguna, beri judul sesuai dengan fungsi fragmen yang sedang aktif.
Ubah judul di panel aplikasi (juga dikenal sebagai panel tindakan) untuk setiap fragmen menggunakan NavController
dan tampilkan tombol Up (←).
- Di
MainActivity.kt
, ganti metodeonCreate()
untuk menyiapkan pengontrol navigasi. Dapatkan instanceNavController
dariNavHostFragment
. - Buat panggilan ke
setupActionBarWithNavController(navController)
yang meneruskan instanceNavController
. Tindakan ini akan melakukan hal berikut: Menampilkan judul di panel aplikasi berdasarkan label tujuan, dan menampilkan tombol Up kapan pun Anda tidak berada di tujuan teratas.
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
setupActionBarWithNavController(navController)
}
}
- Tambahkan impor yang diperlukan saat diminta oleh Android Studio.
import android.os.Bundle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
- Tentukan judul panel aplikasi untuk setiap fragmen. Buka
navigation/nav_graph.xml
dan beralih ke tab Code. - Di
nav_graph.xml
, ubah atributandroid:label
untuk setiap tujuan fragmen. Gunakan resource string berikut yang telah dideklarasikan di aplikasi awal.
Untuk fragmen awal, gunakan @string/app_name
dengan nilai Cupcake
.
Untuk fragmen rasa, gunakan @string/choose_flavor
dengan nilai Choose Flavor
.
Untuk fragmen pengambilan, gunakan @string/choose_pickup_date
dengan nilai Choose Pickup Date
.
Untuk fragmen ringkasan, gunakan @string/order_summary
dengan nilai Order Summary
.
<navigation ...>
<fragment
android:id="@+id/startFragment"
...
android:label="@string/app_name" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/flavorFragment"
...
android:label="@string/choose_flavor" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/pickupFragment"
...
android:label="@string/choose_pickup_date" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/summaryFragment"
...
android:label="@string/order_summary" ... />
</navigation>
- Jalankan aplikasi. Perhatikan bahwa judul di panel aplikasi berubah saat Anda berpindah ke setiap tujuan fragmen. Perhatikan juga bahwa tombol Up (panah ←) kini muncul di panel aplikasi. Jika Anda mengetuknya, tombol tersebut tidak berfungsi. Anda akan menerapkan perilaku tombol Up di codelab berikutnya.
4. Membuat ViewModel bersama
Mari kita lanjutkan dengan mengisi data yang benar di setiap fragmen. Anda akan menggunakan ViewModel
bersama untuk menyimpan data aplikasi dalam satu ViewModel
. Beberapa fragmen di aplikasi akan mengakses ViewModel
bersama menggunakan cakupan aktivitasnya.
Ini adalah kasus penggunaan yang umum untuk berbagi data antarfragmen di kebanyakan aplikasi produksi. Misalnya dalam versi akhir (dari codelab ini) aplikasi Cupcake (lihat screenshot di bawah ini), pengguna memilih jumlah cupcake di layar pertama, dan di layar kedua harga dihitung dan ditampilkan berdasarkan jumlah cupcake tersebut. Demikian pula dengan data aplikasi lain seperti rasa dan tanggal pengambilan juga digunakan di layar ringkasan.
Dengan melihat fitur aplikasi ini, Anda dapat menyimpulkan bahwa lebih baik menyimpan informasi pesanan ini dalam satu ViewModel
, yang dapat dibagikan lintas fragmen di aktivitas ini. Ingat kembali bahwa ViewModel
adalah bagian dari Komponen Arsitektur Android. Data aplikasi yang disimpan di dalam ViewModel
akan dipertahankan selama perubahan konfigurasi. Untuk menambahkan ViewModel
ke aplikasi, Anda perlu membuat class baru yang merupakan lanjutan dari class ViewModel
.
Membuat OrderViewModel
Dalam tugas ini, Anda akan membuat ViewModel
bersama untuk aplikasi Cupcake bernama OrderViewModel
. Anda juga akan menambahkan data aplikasi sebagai properti di dalam ViewModel
serta metode untuk memperbarui dan mengubah data. Berikut adalah properti class-nya:
- Jumlah pesanan (
Integer
) - Rasa cupcake (
String
) - Tanggal pengambilan (
String
) - Harga (
Double
)
Mengikuti praktik terbaik ViewModel
Di ViewModel
, praktik yang direkomendasikan adalah tidak menampilkan data model tampilan sebagai variabel public
. Jika tidak, data aplikasi bisa diubah dengan cara yang tidak diharapkan oleh class eksternal dan membuat kasus ekstrem yang tidak dapat ditangani aplikasi Anda. Sebagai gantinya, jadikan properti yang dapat berubah ini private
, terapkan properti pendukung, dan tampilkan versi public
yang tidak dapat berubah untuk setiap properti, jika diperlukan. Menurut konvensi, tambahkan garis bawah (_
) sebagai awalan pada nama properti private
yang dapat berubah.
Berikut adalah metode untuk memperbarui properti di atas, tergantung pilihan pengguna:
setQuantity(numberCupcakes: Int)
setFlavor(desiredFlavor: String)
setDate(pickupDate: String)
Anda tidak memerlukan metode penyetel untuk harga karena Anda akan menghitungnya di dalam OrderViewModel
menggunakan properti lain. Langkah-langkah di bawah menunjukkan cara menerapkan ViewModel
bersama.
Anda akan membuat paket baru dalam project Anda yang bernama model
dan menambahkan class OrderViewModel
. Tindakan ini akan memisahkan kode model tampilan dari kode UI lainnya (fragmen dan aktivitas). Ini adalah praktik terbaik coding untuk memisahkan kode menjadi paket sesuai dengan fungsinya.
- Di jendela Project Android Studio, klik kanan pada com.example.cupcake > New > Package.
- Dialog New Package akan terbuka, beri paket nama sebagai
com.example.cupcake.model
.
- Buat class Kotlin
OrderViewModel
di bagian paketmodel
. Di jendela Project, klik kanan pada paketmodel
dan pilih New > Kotlin File/Class. Di dialog baru, berikan nama fileOrderViewModel
.
- Di
OrderViewModel.kt
, ubah tanda tangan class agar melanjutkan dariViewModel
.
import androidx.lifecycle.ViewModel
class OrderViewModel : ViewModel() {
}
- Di dalam class
OrderViewModel
, tambahkan properti yang telah dibahas di atas sebagaiprivate
val
. - Ubah jenis properti menjadi
LiveData
dan tambahkan kolom pendukung agar properti tersebut dapat diamati dan UI dapat diperbarui saat data sumber di model tampilan berubah.
private val _quantity = MutableLiveData<Int>(0)
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>("")
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>("")
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>(0.0)
val price: LiveData<Double> = _price
Anda harus mengimpor class ini:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
- Di class
OrderViewModel
, tambahkan metode yang telah dibahas di atas. Di dalam metode, tetapkan argumen yang diteruskan ke properti yang dapat berubah. - Karena metode penyetel ini harus dipanggil dari luar model tampilan, biarkan metode tersebut sebagai metode
public
(artinya tidak adaprivate
atau pengubah visibilitas lain yang diperlukan sebelum kata kuncifun
). Pengubah visibilitas default di Kotlin adalahpublic
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
}
fun setFlavor(desiredFlavor: String) {
_flavor.value = desiredFlavor
}
fun setDate(pickupDate: String) {
_date.value = pickupDate
}
- Build dan jalankan aplikasi Anda untuk memastikan tidak ada error kompilasi. Seharusnya belum ada perubahan yang terlihat di UI Anda.
Bagus! Sekarang Anda telah memulai model tampilan. Anda secara bertahap akan menambahkan lebih banyak hal ke class ini saat membuat lebih banyak fitur di aplikasi Anda, dan saat Anda tahu bahwa Anda memerlukan lebih banyak properti dan metode di class.
Jika Anda melihat nama class, nama properti, atau nama metode dalam font abu-abu di Android Studio, hal itu wajar. Itu artinya, class, properti, atau metode sedang tidak digunakan saat ini, tetapi akan digunakan nanti! Itu adalah topik selanjutnya.
5. Menggunakan ViewModel untuk mengupdate UI
Dalam tugas ini, Anda akan menggunakan model tampilan bersama yang Anda buat untuk mengupdate UI aplikasi. Perbedaan utama dalam penerapan model tampilan bersama adalah cara kita mengaksesnya dari pengontrol UI. Anda akan menggunakan instance aktivitas, bukan instance fragmen, dan Anda akan melihat cara melakukannya di bagian berikutnya.
Itu artinya model tampilan dapat dibagikan lintas fragmen. Setiap fragmen dapat mengakses model tampilan untuk memeriksa beberapa detail pesanan atau memperbarui beberapa data di model tampilan.
Memperbarui StartFragment untuk menggunakan model tampilan
Untuk menggunakan model tampilan bersama di StartFragment
Anda akan melakukan inisialisasi OrderViewModel
menggunakan activityViewModels()
, bukan class delegasi viewModels()
.
viewModels()
memberi Anda instanceViewModel
yang dicakupkan ke fragmen saat ini. Tindakan ini akan berbeda untuk fragmen lain.activityViewModels()
memberi Anda instanceViewModel
yang dicakupkan ke aktivitas saat ini. Oleh karena itu, instance ini akan tetap sama lintas fragmen di aktivitas yang sama.
Menggunakan delegasi properti Kotlin
Di Kotlin, setiap properti (var
) yang dapat berubah memiliki pengambil default dan fungsi penyetel yang otomatis dibuat. Fungsi penyetel dan pengambil dipanggil saat Anda menetapkan nilai atau membaca nilai properti. (Untuk properti hanya baca (val
), hanya fungsi pengambil yang dibuat secara default. Fungsi pengambil dipanggil saat Anda membaca nilai properti hanya baca.)
Delegasi properti di Kotlin membantu Anda menyerahkan tanggung jawab pengambil-penyetel ke class yang berbeda.
Class ini (disebut class delegasi) menyediakan fungsi pengambil dan penyetel dari properti dan menangani perubahannya.
Properti delegasi ditentukan menggunakan klausa by
dan instance class delegasi:
// Syntax for property delegation
var <property-name> : <property-type> by <delegate-class>()
- Di class
StartFragment
, dapatkan referensi ke model tampilan bersama sebagai variabel class. Gunakan delegasi properti Kotlinby activityViewModels()
dari libraryfragment-ktx
.
private val sharedViewModel: OrderViewModel by activityViewModels()
Anda mungkin memerlukan impor baru ini:
import androidx.fragment.app.activityViewModels
import com.example.cupcake.model.OrderViewModel
- Ulangi langkah di atas untuk class
FlavorFragment
,PickupFragment
,SummaryFragment
, Anda akan menggunakan instancesharedViewModel
ini di bagian codelab berikutnya. - Dengan kembali ke class
StartFragment
, Anda sekarang dapat menggunakan model tampilan. Di awal metodeorderCupcake()
, panggil metodesetQuantity()
di model tampilan bersama untuk memperbarui jumlah, sebelum membuka fragmen rasa.
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Di dalam class
OrderViewModel
, tambahkan metode berikut untuk memeriksa apakah rasa untuk pesanan telah ditetapkan atau belum. Anda akan menggunakan metode ini di classStartFragment
pada langkah berikutnya.
fun hasNoFlavorSet(): Boolean {
return _flavor.value.isNullOrEmpty()
}
- Di class
StartFragment
, di dalam metodeorderCupcake()
, setelah menetapkan jumlah, setel rasa default sebagai Vanilla jika tidak ada rasa yang disetel, sebelum membuka fragmen rasa. Metode lengkap Anda akan terlihat seperti ini:
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
if (sharedViewModel.hasNoFlavorSet()) {
sharedViewModel.setFlavor(getString(R.string.vanilla))
}
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Build aplikasi untuk memastikan tidak ada error kompilasi. Namun, seharusnya tidak ada perubahan yang terlihat di UI Anda.
6. Menggunakan ViewModel dengan data binding
Selanjutnya Anda akan menggunakan data binding untuk mengikat data tampilan model ke UI. Anda juga akan memperbarui model tampilan bersama berdasarkan pilihan yang dibuat pengguna di UI.
Pemuat ulang di Data binding
Ingat kembali bahwa Library Data Binding adalah bagian dari Android Jetpack. Data binding mengikat komponen UI di tata letak ke sumber data dalam aplikasi Anda menggunakan format deklaratif. Dalam istilah yang lebih sederhana, data binding adalah mengikat data (dari kode) ke tampilan, dan view binding adalah mengikat tampilan ke kode. Dengan menyiapkan pengikatan ini dan membuat pembaruan bersifat otomatis, hal ini membantu Anda mengurangi peluang error jika Anda lupa untuk secara manual mengupdate UI dari kode Anda.
Memperbarui rasa dengan pilihan pengguna
- Di
layout/fragment_flavor.xml
, tambahkan tag<data>
di dalam tag root<layout>
. Tambahkan variabel tata letak bernamaviewModel
dari jeniscom.example.cupcake.model.OrderViewModel
. Pastikan nama paket di atribut jenis cocok dengan nama paket dari class model tampilan bersama,OrderViewModel
di aplikasi Anda.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Demikian pula, ulangi langkah di atas untuk
fragment_pickup.xml
, danfragment_summary.xml
untuk menambahkan variabel tata letakviewModel
. Anda akan menggunakan variabel ini di bagian berikutnya. Anda tidak perlu menambahkan kode ini difragment_start.xml
, karena tata letak ini tidak menggunakan model tampilan bersama. - Di class
FlavorFragment
, di dalamonViewCreated()
, ikat instance model tampilan dengan instance model tampilan bersama di tata letak tersebut. Tambahkan kode berikut di dalam blokbinding?.
apply
.
binding?.apply {
viewModel = sharedViewModel
...
}
Menerapkan fungsi cakupan
Ini mungkin pertama kalinya Anda melihat fungsi apply
di Kotlin. apply
adalah fungsi cakupan di library standar Kotlin. Tindakan ini menjalankan blok kode di dalam konteks objek. Hal ini membentuk cakupan sementara, dan di cakupan tersebut, Anda dapat mengakses objek tanpa namanya. Kasus penggunaan umum untuk apply
adalah mengonfigurasi objek. Panggilan seperti itu dapat dibaca sebagai "terapkan ketentuan berikut pada objek".
Contoh:
clark.apply {
firstName = "Clark"
lastName = "James"
age = 18
}
// The equivalent code without apply scope function would look like the following.
clark.firstName = "Clark"
clark.lastName = "James"
clark.age = 18
- Ulangi langkah yang sama untuk metode
onViewCreated()
di dalam classPickupFragment
danSummaryFragment
.
binding?.apply {
viewModel = sharedViewModel
...
}
- Di
fragment_flavor.xml
, gunakan variabel tata letak baru,viewModel
untuk menyetel atributchecked
tombol pilihan berdasarkan nilaiflavor
di model tampilan. Jika rasa yang diwakili oleh tombol pilihan sama dengan rasa yang disimpan di model tampilan, maka tampilkan tombol pilihan seperti yang dipilih (checked
= true). Ekspresi binding untuk statusRadioButton
Vanilla yang dicentang akan terlihat seperti berikut:
@{viewModel.flavor.equals(@string/vanilla)}
Pada dasarnya, Anda membandingkan properti viewModel.flavor
dengan resource string yang sesuai menggunakan fungsi equals
, untuk menentukan apakah status yang dicentang harus benar atau salah.
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:checked="@{viewModel.flavor.equals(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:checked="@{viewModel.flavor.equals(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:checked="@{viewModel.flavor.equals(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:checked="@{viewModel.flavor.equals(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:checked="@{viewModel.flavor.equals(@string/coffee)}"
.../>
</RadioGroup>
Binding pemroses
Binding pemroses adalah ekspresi lambda yang dijalankan saat suatu peristiwa terjadi, seperti peristiwa onClick
. Binding ini mirip dengan referensi metode seperti textview.setOnClickListener(clickListener)
tetapi binding pemroses memungkinkan Anda menjalankan ekspresi data binding sembarang.
- Di
fragment_flavor.xml
, tambahkan pemroses peristiwa ke tombol pilihan menggunakan binding pemroses. Gunakan ekspresi lambda tanpa parameter dan lakukan panggilan ke metodeviewModel
.setFlavor()
dengan meneruskan resource string rasa yang sesuai.
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:onClick="@{() -> viewModel.setFlavor(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:onClick="@{() -> viewModel.setFlavor(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:onClick="@{() -> viewModel.setFlavor(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:onClick="@{() -> viewModel.setFlavor(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:onClick="@{() -> viewModel.setFlavor(@string/coffee)}"
.../>
</RadioGroup>
- Jalankan aplikasi dan perhatikan bagaimana opsi Vanilla dipilih secara default di fragmen rasa.
Bagus! Sekarang Anda dapat melanjutkan ke fragmen berikutnya.
7. Mengupdate fragmen pengambilan dan ringkasan untuk menggunakan model tampilan
Jelajahi aplikasi dan perhatikan bahwa di fragmen pengambilan, label opsi tombol pilihan kosong. Dalam tugas ini, Anda akan menghitung 4 tanggal pengambilan yang tersedia dan menampilkannya di fragmen pengambilan. Ada berbagai cara untuk menampilkan tanggal berformat, dan berikut beberapa alat berguna yang disediakan Android untuk melakukannya.
Membuat daftar opsi pengambilan
Pemformat tanggal
Framework Android menyediakan class bernama SimpleDateFormat
, yang merupakan class untuk pemformatan dan penguraian tanggal dengan cara yang sensitif terhadap lokalitas. Hal ini memungkinkan pemformatan (tanggal → teks) dan penguraian (teks → tanggal) pada tanggal.
Anda dapat membuat instance SimpleDateFormat
dengan meneruskan string pola dan lokalitas:
SimpleDateFormat("E MMM d", Locale.getDefault())
String pola seperti "E MMM d"
adalah perwakilan format Tanggal dan Waktu. Huruf dari 'A'
ke 'Z'
dan dari 'a'
ke 'z'
diinterpretasikan sebagai huruf pola yang mewakili komponen tanggal atau string waktu. Misalnya, d
mewakili tanggal dalam sebulan, y
untuk tahun, dan M
untuk bulan. Jika tanggalnya adalah 4 Juli 2018, string pola "EEE, MMM d"
diuraikan menjadi "Wed, Jul 4"
. Untuk daftar lengkap huruf pola, lihat dokumentasi ini.
Objek Locale
mewakili wilayah geografis, politis, atau kultural tertentu. Hal ini mewakili kombinasi bahasa/negara/varian. Lokalitas digunakan untuk mengubah tampilan informasi seperti angka atau tanggal agar sesuai dengan konvensi di wilayah tersebut. Tanggal dan waktu bersifat sensitif terhadap lokalitas, karena keduanya ditulis dengan cara yang berbeda di berbagai belahan dunia. Anda akan menggunakan metode Locale.getDefault()
untuk mengambil kumpulan informasi lokal pada perangkat pengguna dan meneruskannya ke dalam konstruktor SimpleDateFormat
.
Lokalitas di Android adalah kombinasi bahasa dan kode negara. Kode bahasa adalah kode bahasa ISO dengan dua huruf kecil, seperti "en" untuk bahasa Inggris. Kode negara adalah kode negara ISO dengan dua huruf besar, seperti "US" untuk Amerika Serikat.
Sekarang gunakan SimpleDateFormat
dan Locale
untuk menentukan tanggal pengambilan yang tersedia untuk aplikasi Cupcake.
- Di class
OrderViewModel
, tambahkan fungsi berikut bernamagetPickupOptions()
untuk membuat dan menampilkan daftar tanggal pengambilan. Di dalam metode ini, buat variabelval
bernamaoptions
dan lakukan inisialisasi variabel tersebut kemutableListOf
<String>()
.
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
}
- Buat string pemformat menggunakan
SimpleDateFormat
yang meneruskan string pola"E MMM d"
, dan lokalitasnya. Di dalam string pola,E
adalah singkatan dari nama hari dalam seminggu dan diuraikan menjadi "Sel Des 10".
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
Impor java.text.SimpleDateFormat
dan java.util.Locale
, saat diminta oleh Android Studio.
- Dapatkan instance
Calendar
dan tetapkan ke variabel baru. Jadikan instance ini sebagaival
. Variabel ini akan berisi tanggal dan waktu saat ini. Selain itu, imporjava.util.Calendar
.
val calendar = Calendar.getInstance()
- Buat daftar tanggal yang dimulai dengan tanggal saat ini dan tiga tanggal berikutnya. Karena Anda memerlukan 4 opsi tanggal, ulangi blok kode ini 4 kali. Blok
repeat
ini akan memformat tanggal, menambahkannya ke daftar opsi tanggal, lalu secara inkremental menambahkan 1 hari.
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
- Tampilkan
options
yang telah diperbarui di akhir metode ini. Berikut adalah metode lengkap Anda:
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
val calendar = Calendar.getInstance()
// Create a list of dates starting with the current date and the following 3 dates
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
return options
}
- Di class
OrderViewModel
, tambahkan properti class bernamadateOptions
, yaituval
. Lakukan inisialisasi menggunakan metodegetPickupOptions()
yang baru saja Anda buat.
val dateOptions = getPickupOptions()
Memperbarui tata letak untuk menampilkan opsi pengambilan
Setelah Anda memiliki keempat tanggal pengambilan yang tersedia dalam model tampilan, perbarui tata letak fragment_pickup.xml
untuk menampilkan tanggal tersebut. Anda juga akan menggunakan data binding untuk menampilkan status yang dicentang dari setiap tombol pilihan dan memperbarui tanggal di model tampilan saat tombol pilihan lain dipilih. Penerapan ini mirip dengan data binding di fragmen rasa.
Di fragment_pickup.xml
:
Tombol pilihan option0
mewakili dateOptions[0]
di viewModel
(hari ini)
Tombol pilihan option1
mewakili dateOptions[1]
di viewModel
(besok)
Tombol pilihan option2
mewakili dateOptions[2]
di viewModel
(lusa)
Tombol pilihan option3
mewakili dateOptions[3]
di viewModel
(dua hari setelah besok)
- Di
fragment_pickup.xml
, untuk tombol pilihanoption0
, gunakan variabel tata letak baru,viewModel
untuk menetapkan atributchecked
berdasarkan nilaidate
di model tampilan. Bandingkan propertiviewModel.date
dengan string pertama dalam daftardateOptions
, yang merupakan tanggal saat ini. Gunakan fungsiequals
untuk membandingkan dan ekspresi binding akhir terlihat seperti berikut:
@{viewModel.date.equals(viewModel.dateOptions[0])}
- Untuk tombol pilihan yang sama, tambahkan pemroses peristiwa menggunakan binding pemroses ke atribut
onClick
. Saat opsi tombol pilihan ini diklik, lakukan panggilan kesetDate()
diviewModel
, yang meneruskandateOptions[0]
. - Untuk tombol pilihan yang sama, setel nilai atribut
text
ke string pertama dalam daftardateOptions
.
<RadioButton
android:id="@+id/option0"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[0])}"
android:text="@{viewModel.dateOptions[0]}"
...
/>
- Ulangi langkah-langkah di atas untuk tombol pilihan lainnya, ubah indeks
dateOptions
yang sesuai.
<RadioButton
android:id="@+id/option1"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[1])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[1])}"
android:text="@{viewModel.dateOptions[1]}"
... />
<RadioButton
android:id="@+id/option2"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[2])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[2])}"
android:text="@{viewModel.dateOptions[2]}"
... />
<RadioButton
android:id="@+id/option3"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[3])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[3])}"
android:text="@{viewModel.dateOptions[3]}"
... />
- Jalankan aplikasi dan Anda akan melihat beberapa hari mendatang sebagai opsi pengambilan yang tersedia. Screenshot Anda akan berbeda, tergantung pada waktu Anda mencoba tugas ini. Perhatikan bahwa tidak ada opsi yang dipilih secara default. Anda akan menerapkan hal ini di langkah berikutnya.
- Di dalam class
OrderViewModel
, buat fungsi bernamaresetOrder()
, untuk mereset propertiMutableLiveData
di model tampilan. Tetapkan nilai tanggal saat ini dari daftardateOptions
ke_date.
value.
fun resetOrder() {
_quantity.value = 0
_flavor.value = ""
_date.value = dateOptions[0]
_price.value = 0.0
}
- Tambahkan blok
init
ke class, dan panggil metoderesetOrder()
baru dari blok tersebut.
init {
resetOrder()
}
- Hapus nilai awal dari deklarasi properti di class. Sekarang Anda menggunakan blok
init
untuk melakukan inisialisasi properti saat instanceOrderViewModel
dibuat.
private val _quantity = MutableLiveData<Int>()
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>()
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>()
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>()
val price: LiveData<Double> = _price
- Jalankan aplikasi Anda lagi, perhatikan bahwa tanggal hari ini dipilih secara default.
Memperbarui fragmen Ringkasan untuk menggunakan model tampilan
Sekarang mari kita beralih ke fragmen terakhir. Fragmen ringkasan pesanan dimaksudkan untuk menampilkan ringkasan detail pesanan. Dalam tugas ini, Anda memanfaatkan semua informasi pesanan dari model tampilan bersama dan memperbarui detail pesanan di layar menggunakan data binding.
- Di
fragment_summary.xml
, pastikan variabel data model tampilanviewModel
sudah dideklarasikan.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Di
SummaryFragment
, dionViewCreated()
, pastikanbinding.viewModel
sudah diinisialisasi. - Di
fragment_summary.xml
, baca dari model tampilan untuk memperbarui layar dengan detail ringkasan pesanan. PerbaruiTextViews
jumlah, rasa, dan tanggal dengan menambahkan atribut teks berikut. Jumlah dari jenisInt
, sehingga Anda perlu mengonversinya menjadi string.
<TextView
android:id="@+id/quantity"
...
android:text="@{viewModel.quantity.toString()}"
... />
<TextView
android:id="@+id/flavor"
...
android:text="@{viewModel.flavor}"
... />
<TextView
android:id="@+id/date"
...
android:text="@{viewModel.date}"
... />
- Jalankan dan uji aplikasi untuk memastikan bahwa opsi pesanan yang Anda pilih muncul di ringkasan pesanan.
8. Menghitung harga dari detail pesanan
Dengan melihat screenshot aplikasi akhir di codelab ini, Anda akan melihat bahwa harganya benar-benar ditampilkan pada setiap fragmen (kecuali StartFragment
) sehingga pengguna tahu harganya sepanjang proses pemesanan.
Berikut adalah aturan dari toko cupcake kita tentang cara menghitung harga.
- Setiap cupcake berharga $2,00
- Pengambilan di hari yang sama menambahkan $3,00 ke pesanan
Oleh karena itu, untuk pesanan 6 cupcake, harganya adalah 6 cupcake x $2 = $12. Jika pengguna menginginkan pengambilan di hari yang sama, biaya tambahan sebesar $3 akan membuat total harga pesanan menjadi $15.
Memperbarui harga di model tampilan
Untuk menambahkan dukungan untuk fungsi ini di aplikasi Anda, selesaikan terlebih dahulu harga per cupcake dan abaikan biaya pengambilan di hari yang sama.
- Buka
OrderViewModel.kt
, dan simpan harga per cupcake di satu variabel. Deklarasikan sebagai konstanta pribadi tingkat teratas di bagian atas file, di luar definisi class (tetapi setelah pernyataan impor). Gunakan pengubahconst
dan untuk menjadikannya hanya baca gunakanval
.
package ...
import ...
private const val PRICE_PER_CUPCAKE = 2.00
class OrderViewModel : ViewModel() {
...
Ingat kembali bahwa nilai konstanta tersebut (yang ditandai dengan kata kunci const
di Kotlin) tidak berubah dan nilainya diketahui pada waktu kompilasi. Untuk mempelajari konstanta selengkapnya, baca dokumentasi ini.
- Setelah Anda menentukan harga per cupcake, buat metode helper untuk menghitung harganya. Metode ini dapat berupa
private
karena hanya digunakan di dalam class ini. Anda akan mengubah logika harga untuk menyertakan biaya pengambilan di hari yang sama pada tugas berikutnya.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
}
Baris kode ini mengalikan harga per cupcake dengan jumlah cupcake yang dipesan. Untuk kode dalam tanda kurung, karena nilai quantity.value
bisa null, gunakan operator elvis (?:
). Operator elvis (?:
) berarti bahwa jika ekspresi di sebelah kiri bukan null, maka gunakan operator tersebut. Jika ekspresi di sebelah kiri adalah null, gunakan ekspresi di sebelah kanan operator elvis (yang dalam kasus ini adalah 0
).
- Pada class
OrderViewModel
yang sama, perbarui variabel harga saat jumlahnya ditetapkan. Lakukan panggilan ke fungsi baru di fungsisetQuantity()
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
updatePrice()
}
Mengikat properti harga ke UI
- Di tata letak untuk
fragment_flavor.xml
,fragment_pickup.xml
, danfragment_summary.xml
, pastikan variabel dataviewModel
dari jeniscom.example.cupcake.model.OrderViewModel
ditentukan.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Di metode
onViewCreated()
setiap class fragmen, pastikan Anda mengikat instance objek model tampilan di fragmen tersebut ke variabel data model tampilan di tata letak.
binding?.apply {
viewModel = sharedViewModel
...
}
- Di dalam setiap tata letak fragmen, gunakan variabel
viewModel
untuk menetapkan harga jika ditampilkan di tata letak. Mulai dengan memodifikasi filefragment_flavor.xml
. Untuk tampilan tekssubtotal
, setel nilai atributandroid:text
menjadi"@{@string/subtotal_price(viewModel.price)}".
Ekspresi tata letak data binding ini menggunakan resource string@string/subtotal_price
dan diteruskan di parameter, yaitu harga dari model tampilan, sehingga output-nya akan menampilkan Subtotal 12.0, misalnya.
...
<TextView
android:id="@+id/subtotal"
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
Anda menggunakan resource string ini yang telah dideklarasikan di file strings.xml
:
<string name="subtotal_price">Subtotal %s</string>
- Jalankan aplikasi. Jika Anda memilih Satu cupcake di fragmen awal, fragmen rasa akan menampilkan Subtotal 2.0. Jika Anda memilih Enam cupcake, fragmen rasa akan menampilkan Subtotal 12.0, dan seterusnya. Anda akan memformat harga ke dalam format mata uang yang sesuai nanti, sehingga perilaku ini wajar untuk saat ini.
- Sekarang buat perubahan yang serupa untuk fragmen pengambilan dan ringkasan. Di tata letak
fragment_pickup.xml
danfragment_summary.xml
, ubah tampilan teks untuk menggunakan propertiviewModel
price
juga.
fragment_pickup.xml
...
<TextView
android:id="@+id/subtotal"
...
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
fragment_summary.xml
...
<TextView
android:id="@+id/total"
...
android:text="@{@string/total_price(viewModel.price)}"
... />
...
- Jalankan aplikasi. Pastikan harga yang ditampilkan dalam ringkasan pesanan dihitung dengan benar untuk jumlah pesanan 1, 6, dan 12 cupcake. Seperti yang telah disebutkan, pemformatan harga yang tidak benar saat ini merupakan hal yang wajar (formatnya akan ditampilkan sebagai 2.0 untuk $2 atau 12.0 untuk $12).
Mengenakan biaya ekstra untuk pengambilan di hari yang sama
Dalam tugas ini, Anda akan menerapkan aturan kedua, yaitu pengambilan di hari yang sama akan menambahkan $3,00 ke pesanan.
- Di class
OrderViewModel
, tentukan konstanta pribadi tingkat teratas yang baru untuk biaya pengambilan di hari yang sama.
private const val PRICE_FOR_SAME_DAY_PICKUP = 3.00
- Di
updatePrice()
, periksa apakah pengguna memilih pengambilan di hari yang sama. Periksa apakah tanggal di model tampilan (_date.
value
) sama dengan item pertama di daftardateOptions
yang selalu berisi hari ini.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
if (dateOptions[0] == _date.value) {
}
}
- Untuk mempermudah penghitungan ini, perkenalkan variabel sementara,
calculatedPrice
. Hitung harga terbaru dan tetapkan kembali ke_price.
value
.
private fun updatePrice() {
var calculatedPrice = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
// If the user selected the first option (today) for pickup, add the surcharge
if (dateOptions[0] == _date.value) {
calculatedPrice += PRICE_FOR_SAME_DAY_PICKUP
}
_price.value = calculatedPrice
}
- Panggil metode helper
updatePrice()
dari metodesetDate()
untuk menambahkan biaya pengambilan di hari yang sama.
fun setDate(pickupDate: String) {
_date.value = pickupDate
updatePrice()
}
- Jalankan aplikasi Anda, jelajahi aplikasi. Anda akan melihat bahwa mengubah tanggal pengambilan tidak akan menghapus biaya pengambilan di hari yang sama dari harga total. Hal ini karena harga diubah di model tampilan tetapi tidak diberitahukan ke tata letak binding.
Menetapkan pemilik Lifecycle untuk mengamati LiveData
LifecycleOwner
adalah class yang memiliki siklus proses Android, seperti aktivitas atau fragmen. Pengamat LiveData
mengamati perubahan pada data aplikasi hanya jika pemilik siklus proses berstatus aktif (STARTED
atau RESUMED
).
Di aplikasi Anda, objek LiveData
atau data yang dapat diamati adalah properti price
di model tampilan. Pemilik siklus proses adalah fragmen rasa, pengambilan, dan ringkasan. Pengamat LiveData
adalah ekspresi binding di file tata letak dengan data yang dapat diamati seperti harga. Dengan Data Binding, saat nilai yang dapat diamati berubah, elemen UI yang terikat akan terupdate secara otomatis.
Contoh ekspresi binding: android:text="@{@string/subtotal_price(viewModel.price)}"
Agar elemen UI dapat diperbarui secara otomatis, Anda harus mengaitkan binding.
lifecycleOwner
dengan pemilik siklus proses dalam aplikasi. Anda akan menerapkan ini nanti.
- Di class
FlavorFragment
,PickupFragment
,SummaryFragment
, di dalam metodeonViewCreated()
, tambahkan kode berikut di blokbinding?.apply
. Tindakan ini akan menetapkan pemilik siklus proses pada objek binding. Dengan menetapkan pemilik siklus proses, aplikasi akan dapat mengamati objekLiveData
.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
...
}
- Jalankan aplikasi Anda lagi. Di layar pengambilan, ubah tanggal pengambilan dan perhatikan perbedaannya bagaimana harga berubah secara otomatis. Dan biaya pengambilan ditampilkan dengan benar di layar ringkasan.
- Perhatikan bahwa jika Anda memilih tanggal pengambilan hari ini, harga pesanan bertambah $3,00. Harga untuk memilih tanggal mendatang akan tetap berupa jumlah cupcake x $2,00.
- Uji berbagai kasus dengan jumlah, rasa, dan tanggal pengambilan yang lain. Sekarang Anda akan melihat harga diperbarui dari model tampilan di setiap fragmen. Sisi terbaiknya adalah Anda tidak perlu menulis kode Kotlin tambahan agar UI selalu diperbarui dengan harganya setiap saat.
Untuk menyelesaikan penerapan fitur harga, Anda perlu memformat harga ke mata uang lokal.
Memformat harga dengan transformasi LiveData
Metode transformasi LiveData
menyediakan cara untuk melakukan manipulasi data pada LiveData
sumber dan menampilkan objek LiveData
yang dihasilkan. Secara sederhana, metode ini mengubah nilai LiveData
menjadi nilai lain. Transformasi ini tidak dihitung kecuali jika pengamat mengamati objek LiveData
.
Transformations.map()
adalah salah satu fungsi transformasi, metode ini mengambil LiveData
sumber dan suatu fungsi sebagai parameter. Fungsi ini memanipulasi LiveData
sumber dan menampilkan nilai yang telah diperbarui, yang juga dapat diamati.
Beberapa contoh real-time tempat Anda dapat menggunakan transformasi LiveData:
- Memformat tanggal dan string waktu untuk tampilan
- Mengurutkan daftar item
- Memfilter atau mengelompokkan item
- Menghitung hasil dari daftar seperti total semua item, jumlah item, pengembalian item terakhir, dan sebagainya.
Dalam tugas ini, Anda akan menggunakan metode Transformations.map()
untuk memformat harga menggunakan mata uang lokal. Anda akan mengubah harga asli sebagai nilai desimal (LiveData<Double>
) menjadi nilai string (LiveData<String>
).
- Di class
OrderViewModel
, ubah jenis properti pendukung keLiveData<String>
, bukanLiveData<Double>.
Harga berformat akan berupa string dengan simbol mata uang seperti '$'. Anda akan memperbaiki kesalahan inisialisasi di langkah berikutnya.
private val _price = MutableLiveData<Double>()
val price: LiveData<String>
- Gunakan
Transformations.map()
untuk melakukan inisialisasi variabel baru, teruskan_price
dan fungsi lambda. Gunakan metodegetCurrencyInstance()
di classNumberFormat
untuk mengonversi harga ke format mata uang lokal. Kode transformasi akan terlihat seperti ini.
private val _price = MutableLiveData<Double>()
val price: LiveData<String> = Transformations.map(_price) {
NumberFormat.getCurrencyInstance().format(it)
}
Anda harus mengimpor androidx.lifecycle.Transformations
dan java.text.NumberFormat
.
- Jalankan aplikasi. Anda akan melihat string harga berformat untuk subtotal dan total. Ini jauh lebih mudah digunakan!
- Uji apakah implementasi tersebut berfungsi seperti yang diharapkan. Uji kasus seperti: Pesan satu cupcake, pesan enam cupcake, pesan 12 cupcake. Pastikan harganya diperbarui dengan benar pada setiap layar. Pada layar akan tertulis Subtotal $2.00 untuk fragmen Rasa dan Pengambilan, dan Total $2.00 untuk ringkasan pesanan. Selain itu, pastikan ringkasan pesanan menampilkan detail pesanan yang benar.
9. Menyiapkan pemroses klik menggunakan binding pemroses
Dalam tugas ini, Anda akan menggunakan binding pemroses untuk mengikat pemroses klik tombol di class fragmen ke tata letak.
- Pada file tata letak
fragment_start.xml
, tambahkan variabel data bernamastartFragment
dari jeniscom.example.cupcake.StartFragment
. Pastikan nama paket fragmen cocok dengan nama paket aplikasi Anda.
<layout ...>
<data>
<variable
name="startFragment"
type="com.example.cupcake.StartFragment" />
</data>
...
<ScrollView ...>
- Di
StartFragment.kt
, di metodeonViewCreated()
, ikat variabel data baru ke instance fragmen. Anda dapat mengakses instance fragmen di dalam fragmen menggunakan kata kuncithis
. Hapus blokbinding?.
apply
beserta kode di dalamnya. Metode lengkapnya akan terlihat seperti ini.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.startFragment = this
}
- Di
fragment_start.xml
, tambahkan pemroses peristiwa menggunakan binding pemroses ke atributonClick
untuk tombol, lakukan panggilan keorderCupcake()
padastartFragment
, yang meneruskan jumlah cupcake.
<Button
android:id="@+id/order_one_cupcake"
android:onClick="@{() -> startFragment.orderCupcake(1)}"
... />
<Button
android:id="@+id/order_six_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(6)}"
... />
<Button
android:id="@+id/order_twelve_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(12)}"
... />
- Jalankan aplikasi. Perhatikan bahwa pemroses klik tombol di fragmen awal berfungsi seperti yang diharapkan.
- Demikian pula, tambahkan variabel data di atas di tata letak lain juga untuk mengikat instance fragmen,
fragment_flavor.xml
,fragment_pickup.xml
, danfragment_summary.xml
.
Di fragment_flavor.xml
<layout ...>
<data>
<variable
... />
<variable
name="flavorFragment"
type="com.example.cupcake.FlavorFragment" />
</data>
<ScrollView ...>
Di fragment_pickup.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="pickupFragment"
type="com.example.cupcake.PickupFragment" />
</data>
<ScrollView ...>
Di fragment_summary.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="summaryFragment"
type="com.example.cupcake.SummaryFragment" />
</data>
<ScrollView ...>
- Di class fragmen lainnya, di metode
onViewCreated()
, hapus kode yang secara manual menetapkan pemroses klik pada tombol. - Di metode
onViewCreated()
, ikat variabel data fragmen dengan instance fragmen. Anda akan menggunakan kata kuncithis
dengan cara yang berbeda di sini, karena di dalam blokbinding?.apply
, kata kuncithis
merujuk pada instance binding, bukan instance fragmen. Gunakan@
dan secara eksplisit tentukan nama class fragmen, misalnyathis@FlavorFragment
. MetodeonViewCreated()
lengkapnya akan terlihat seperti berikut:
Metode onViewCreated()
di class FlavorFragment
akan terlihat seperti ini:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
flavorFragment = this@FlavorFragment
}
}
Metode onViewCreated()
di class PickupFragment
akan terlihat seperti ini:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
pickupFragment = this@PickupFragment
}
}
Metode onViewCreated()
yang dihasilkan di metode class SummaryFragment
akan terlihat seperti ini:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
summaryFragment = this@SummaryFragment
}
}
- Demikian pula, di file tata letak lainnya, tambahkan ekspresi binding pemroses ke atribut
onClick
untuk tombol tersebut.
Di fragment_flavor.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> flavorFragment.goToNextScreen()}"
... />
Di fragment_pickup.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> pickupFragment.goToNextScreen()}"
... />
Di fragment_summary.xml
:
<Button
android:id="@+id/send_button"
android:onClick="@{() -> summaryFragment.sendOrder()}"
...>
- Jalankan aplikasi untuk memastikan tombol masih berfungsi seperti yang diharapkan. Seharusnya tidak ada perubahan perilaku yang terlihat, tetapi kini Anda telah menggunakan binding pemroses untuk menyiapkan pemroses klik!
Selamat telah menyelesaikan codelab ini dan membuat aplikasi Cupcake! Namun, aplikasi ini belum selesai sepenuhnya. Di codelab berikutnya, Anda akan menambahkan tombol Cancel dan memodifikasi backstack. Anda juga akan mempelajari apa itu backstack dan topik baru lainnya. Sampai jumpa!
10. Kode solusi
Kode solusi untuk codelab ini ada dalam project yang ditampilkan di bawah. Gunakan cabang viewmodel untuk mengambil atau mendownload kodenya.
Untuk mendapatkan kode codelab ini dan membukanya di Android Studio, lakukan hal berikut.
Mendapatkan kode
- Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
- Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.
- Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
- Temukan file di komputer Anda (mungkin di folder Downloads).
- Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.
Membuka project di Android Studio
- Mulai Android Studio.
- Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.
Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.
- Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
- Klik dua kali pada folder project tersebut.
- Tunggu Android Studio membuka project.
- Klik tombol Run untuk membangun dan menjalankan aplikasi. Pastikan aplikasi dibangun seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi disiapkan.
11. Ringkasan
ViewModel
adalah bagian dari Komponen Arsitektur Android dan data aplikasi yang disimpan di dalamViewModel
tidak akan hilang selama perubahan konfigurasi. Untuk menambahkanViewModel
ke aplikasi, Anda dapat membuat class baru dan melanjutkannya dari classViewModel
.ViewModel
bersama digunakan untuk menyimpan data aplikasi dari beberapa fragmen dalam satuViewModel
. Beberapa fragmen di aplikasi akan mengaksesViewModel
bersama menggunakan cakupan aktivitasnya.LifecycleOwner
adalah class yang memiliki siklus proses Android, seperti aktivitas atau fragmen.- Pengamat
LiveData
mengamati perubahan pada data aplikasi hanya jika pemilik siklus proses berstatus aktif (STARTED
atauRESUMED
). - Binding pemroses adalah ekspresi lambda yang dijalankan saat suatu peristiwa terjadi, seperti peristiwa
onClick
. Binding ini mirip dengan referensi metode sepertitextview.setOnClickListener(clickListener)
tetapi binding pemroses memungkinkan Anda menjalankan ekspresi data binding sembarang. - Metode transformasi
LiveData
menyediakan cara untuk melakukan manipulasi data padaLiveData
sumber dan menampilkan objekLiveData
yang dihasilkan. - Framework Android menyediakan class bernama
SimpleDateFormat
, yang merupakan class untuk pemformatan dan penguraian tanggal dengan cara yang sensitif terhadap lokalitas. Hal ini memungkinkan pemformatan (tanggal → teks) dan penguraian (teks → tanggal) pada tanggal.