Pengantar
Pada codelab sebelumnya, Anda telah mempelajari cara mendapatkan data dari layanan web dan mengurai respons menjadi objek Kotlin. Dalam codelab ini, Anda membangun pengetahuan tersebut untuk memuat dan menampilkan foto dari URL web. Anda juga meninjau kembali cara membuat RecyclerView
dan menggunakannya untuk menampilkan petak gambar pada halaman ringkasan.
Prasyarat
- Cara membuat dan menggunakan fragmen.
- Cara mengambil JSON dari layanan web REST dan mengurai data tersebut ke dalam objek Kotlin menggunakan library Retrofit dan Moshi.
- Cara membuat tata letak petak dengan
RecyclerView
. - Cara kerja
Adapter
,ViewHolder
, danDiffUtil
.
Yang akan Anda pelajari
- Cara menggunakan library Coil untuk memuat dan menampilkan gambar dari URL web.
- Cara menggunakan
RecyclerView
dan adaptor petak untuk menampilkan petak gambar. - Cara menangani potensi error saat gambar didownload dan ditampilkan.
Yang akan Anda buat
- Modifikasi aplikasi MarsPhotos untuk mendapatkan URL gambar dari data Mars, dan gunakan Coil untuk memuat dan menampilkan gambar itu.
- Tambahkan animasi pemuatan dan ikon error ke aplikasi.
- Gunakan
RecyclerView
untuk menampilkan petak gambar Mars. - Tambahkan penanganan status dan error ke
RecyclerView
.
Yang Anda perlukan
- Komputer dengan browser web modern, seperti Chrome versi terbaru.
- Akses internet di komputer.
Dalam codelab ini, Anda akan terus bekerja dengan aplikasi dari codelab sebelumnya yang disebut MarsPhotos. Aplikasi MarsPhotos terhubung ke layanan web untuk mengambil dan menampilkan jumlah objek Kotlin yang diambil menggunakan Retrofit. Objek Kotlin ini berisi URL foto kehidupan nyata dari permukaan Mars yang diambil dari penjelajah Mars NASA.
Versi aplikasi yang akan Anda buat dalam codelab ini akan terisi di halaman ringkasan, yang menampilkan foto Mars dalam petak gambar. Gambar adalah bagian dari data yang diambil aplikasi Anda dari layanan web Mars. Aplikasi Anda akan menggunakan library Coil untuk memuat dan menampilkan gambar, serta RecyclerView
untuk membuat tata letak petak untuk gambar. Aplikasi Anda juga akan menangani error jaringan dengan baik.
Menampilkan foto dari URL web mungkin terdengar mudah, tetapi ada sedikit teknik untuk membuatnya berfungsi dengan baik. Gambar harus didownload, disimpan secara internal, dan didekode-kan dari format terkompresi menjadi gambar yang dapat digunakan Android. Gambar harus di-cache ke cache dalam memori, cache berbasis penyimpanan, atau keduanya. Semua ini harus terjadi dalam thread latar belakang prioritas rendah sehingga UI tetap responsif. Selain itu, untuk performa jaringan dan CPU terbaik, Anda dapat mengambil dan mendekode lebih dari satu gambar sekaligus.
Untungnya, Anda dapat menggunakan library yang dikembangkan komunitas yang disebut Coil untuk mendownload, buffer, mendekode, dan meng-cache gambar Anda. Tanpa menggunakan Coil, Anda akan memiliki lebih banyak pekerjaan yang harus dilakukan.
Coil pada dasarnya memerlukan dua hal:
- URL gambar yang ingin Anda muat dan tampilkan.
- Objek
ImageView
untuk benar-benar menampilkan gambar tersebut.
Dalam tugas ini, Anda akan mempelajari cara menggunakan Coil untuk menampilkan satu gambar dari layanan web Mars. Anda menampilkan gambar foto Mars pertama dalam daftar foto yang ditampilkan oleh layanan web. Berikut adalah screenshot sebelum dan sesudah:
Menambahkan dependensi Coil
- Buka aplikasi MarsPhotos dari codelab sebelumnya.
- Jalankan aplikasi untuk melihat fungsinya. (Ini menunjukkan jumlah total foto Mars yang diambil).
- Buka build.gradle (Module: app).
- Di bagian
dependencies
, tambahkan baris ini untuk library Coil:
// Coil
implementation "io.coil-kt:coil:1.1.1"
Periksa dan update versi terbaru library dari halaman dokumentasi Coil.
- Library Coil dihosting dan tersedia di repositori
mavenCentral()
. Di build.gradle (Project: MarsPhotos), tambahkanmavenCentral()
di blokrepositories
atas.
repositories {
google()
jcenter()
mavenCentral()
}
- Klik Sync Now untuk membuat ulang project dengan dependensi baru.
Mengupdate ViewModel
Pada langkah ini, Anda akan menambahkan properti LiveData
ke class OverviewViewModel
untuk menyimpan objek Kotlin yang diterima, MarsPhoto.
- Buka
overview/OverviewViewModel.kt
. Tepat di bawah deklarasi properti_status
, tambahkan properti baru yang dapat berubah yang disebut_photos
, dari jenisMutableLiveData
yang dapat menyimpan objekMarsPhoto
tunggal.
private val _photos = MutableLiveData<MarsPhoto>()
Impor com.example.android.marsphotos.network.MarsPhoto
saat diminta.
- Tepat di bawah deklarasi
_photos
, tambahkan kolom dukungan publik yang disebutphotos
dari jenisnya,LiveData<MarsPhoto>
.
val photos: LiveData<MarsPhoto> = _photos
- Pada metode
getMarsPhotos()
, di dalam bloktry{}
, temukan baris yang menyetel data yang diambil dari layanan web menjadilistResult.
try {
val listResult = MarsApi.retrofitService.getPhotos()
...
}
- Tetapkan foto Mars pertama yang diambil ke variabel baru
_photos
. UbahlistResult
menjadi_photos.value
. Tetapkan url foto pertama pada indeks0
. Ini akan memunculkan error, Anda akan memperbaikinya nanti.
try {
_photos.value = MarsApi.retrofitService.getPhotos()[0]
...
}
- Di baris berikutnya, update
status.value
menjadi yang berikut. Gunakan data dari properti baru, bukanlistResult
. Tampilkan URL gambar pertama dari Daftar foto.
try {
...
_status.value = " First Mars image URL : ${_photos.value!!.imgSrcUrl}"
}
- Blok lengkap
try{}
sekarang terlihat seperti ini:
try {
_photos.value = MarsApi.retrofitService.getPhotos()[0]
_status.value = " First Mars image URL : ${_photos.value!!.imgSrcUrl}"
}
- Jalankan aplikasi.
TextView
sekarang menampilkan URL foto Mars pertama. Semua yang telah Anda lakukan sejauh ini adalah menyiapkanViewModel
danLiveData
untuk URL tersebut.
Menggunakan Adaptor Binding
Adaptor Binding adalah metode beranotasi yang digunakan untuk membuat penyetel khusus untuk properti kustom tampilan Anda.
Biasanya saat Anda menetapkan atribut dalam XML menggunakan kode: android:text="Sample Text"
. Sistem Android otomatis mencari penyetel dengan nama yang sama dengan atribut text
, yang ditetapkan oleh metode setText(String: text)
. Metode setText(String: text)
adalah metode penyetel untuk beberapa tampilan yang disediakan oleh Framework Android. Perilaku serupa dapat disesuaikan menggunakan adaptor binding; Anda dapat memberikan atribut khusus dan logika khusus yang akan dipanggil oleh library Data binding.
Contoh:
Untuk melakukan sesuatu yang lebih kompleks daripada hanya memanggil penyetel pada tampilan Gambar, yang menetapkan gambar drawable. Pertimbangkan untuk memuat gambar dari UI thread (thread utama), dari internet. Pertama, pilih atribut khusus untuk menetapkan gambar ke ImageView
. Pada contoh berikut adalah imageUrl
.
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:imageUrl="@{product.imageUrl}"/>
Jika Anda tidak menambahkan kode lebih lanjut, sistem akan mencari metode setImageUrl(String)
di ImageView
dan tidak menemukannya, sehingga menimbulkan error karena ini adalah atribut khusus yang tidak disediakan oleh framework. Anda harus membuat cara untuk menerapkan dan menetapkan atribut app:imageUrl
ke ImageView
. Anda akan menggunakan adaptor Binding (metode beranotasi) untuk melakukannya.
Contoh Adaptor Binding:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
// Load the image in the background using Coil.
}
}
}
Anotasi @BindingAdapter
mengambil nama atribut sebagai parameternya.
Pada metode bindImage
, parameter metode pertama adalah jenis Tampilan target dan yang kedua adalah nilai yang ditetapkan ke atribut.
Di dalam metode tersebut, library Coil memuat gambar dari UI thread dan menyetelnya ke ImageView
.
Membuat adaptor binding dan menggunakan Coil
- Dalam paket
com.example.android.marsphotos
, buat file Kotlin bernamaBindingAdapters
. File ini akan menyimpan adaptor binding yang Anda gunakan di seluruh aplikasi.
- Di
BindingAdapters.kt
. Buat fungsibindImage()
yang menggunakan parameterImageView
danString
.
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
Impor android.widget.ImageView
bila diminta.
- Anotasi fungsi dengan
@BindingAdapter
. Anotasi@BindingAdapter
memberi tahu data binding untuk mengeksekusi adaptor binding ini jika item Tampilan memiliki atributimageUrl
.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
Impor androidx.databinding.BindingAdapter
bila diminta.
biarkan fungsi cakupan
let
adalah salah satu fungsi Cakupan Kotlin yang memungkinkan Anda mengeksekusi blok kode dalam konteks objek. Ada lima fungsi Cakupan di Kotlin, lihat dokumentasi untuk mempelajari lebih lanjut.
Penggunaan:
let
digunakan untuk memanggil satu atau beberapa fungsi pada hasil rantai panggilan.
Fungsi let
bersama operator panggilan aman( ?.
) digunakan untuk melakukan pengoperasian aman null pada objek. Dalam hal ini, blok kode let
hanya akan dijalankan jika objek tidak bernilai null.
- Di dalam fungsi
bindImage()
, tambahkan bloklet{}
ke argumenimageURL
, menggunakan operator panggilan aman.
imgUrl?.let {
}
- Di dalam blok
let{}
, tambahkan baris berikut untuk mengonversi string URL menjadi objekUri
menggunakan metodetoUri()
. Untuk menggunakan skema HTTPS, tambahkanbuildUpon.scheme("https")
ke buildertoUri
. Panggilbuild()
untuk membuat objek.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Impor androidx.core.net.toUri
bila diminta.
- Di dalam blok
let{}
, setelah deklarasiimgUri
, gunakanload(){}
dari Coil, untuk memuat gambar dari objekimgUri
keimgView
.
imgView.load(imgUri) {
}
Impor coil.load
bila diminta.
- Metode lengkap Anda akan terlihat seperti di bawah ini:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
imgView.load(imgUri)
}
}
Memperbarui tata letak dan fragmen
Di bagian sebelumnya, Anda menggunakan library gambar Coil untuk memuat gambar. Untuk melihat gambar di layar, langkah selanjutnya adalah memperbarui ImageView
dengan atribut baru untuk menampilkan satu gambar.
Nanti di codelab, Anda akan menggunakan res/layout/grid_view_item.xml
sebagai file resource tata letak setiap item petak di RecyclerView
. Dalam tugas ini, Anda akan menggunakan file ini untuk sementara guna menampilkan gambar menggunakan URL gambar yang diambil pada tugas sebelumnya. Untuk sementara, Anda akan menggunakan file tata letak ini untuk menggantikan fragment_overview.xml
.
- Buka
res/layout/grid_view_item.xml
- Di atas elemen
<ImageView>
, tambahkan elemen<data>
untuk data binding, dan binding ke classOverviewViewModel
:
<data>
<variable
name="viewModel"
type="com.example.android.marsphotos.overview.OverviewViewModel" />
</data>
- Tambahkan atribut
app:imageUrl
ke elemenImageView
untuk menggunakan adaptor binding pemuatan gambar baru. Ingatlah bahwaphotos
berisi daftarMarsPhotos
yang diambil dari server. Tetapkan URL entri pertama ke atributimageUrl
.
<ImageView
android:id="@+id/mars_image"
...
app:imageUrl="@{viewModel.photos.imgSrcUrl}"
... />
- Buka
overview/OverviewFragment.kt
. Dalam metodeonCreateView()
, komentari baris yang meng-inflate classFragmentOverviewBinding
dan menetapkannya ke variabel binding. Anda akan melihat error karena penghapusan baris ini. Ini hanya sementara; Anda akan memperbaikinya nanti.
//val binding = FragmentOverviewBinding.inflate(inflater)
- Gunakan
grid_view_item.xml
, bukanfragment_overview.xml.
Tambahkan baris berikut untuk meng-inflate classGridViewItemBinding
.
val binding = GridViewItemBinding.inflate(inflater)
Impor com.example.android.marsphotos. databinding.GridViewItemBinding
bila diminta.
- Jalankan aplikasi. Anda sekarang akan melihat satu gambar Mars.
Menambahkan gambar pemuatan dan error
Penggunaan Coil Anda dapat meningkatkan pengalaman pengguna dengan menampilkan gambar placeholder saat memuat gambar dan gambar error jika pemuatan gagal, misalnya jika gambar hilang atau rusak. Pada langkah ini, Anda akan menambahkan fungsi tersebut ke adaptor binding.
- Buka
res/drawable/ic_broken_image.xml
, lalu klik tab Design di sebelah kanan. Untuk gambar error, Anda menggunakan ikon gambar rusak yang tersedia di library ikon bawaan. Vektor drawable ini menggunakan atributandroid:tint
untuk mewarnai ikon menjadi abu-abu.
- Buka
res/drawable/loading_animation.xml
Drawable ini adalah animasi yang memutar drawable gambar,loading_img.xml
, di sekitar titik tengah. (Anda tidak melihat animasi di pratinjau.) d
- Kembali ke file
BindingAdapters.kt
. Dalam metodebindImage()
, perbarui panggilan keimgView.
load
(imgUri)
untuk menambahkan lambda trailing sebagai berikut: Kode ini menetapkan placeholder memuat gambar untuk digunakan saat memuat (drawableloading_animation
). Kode ini juga menyetel gambar untuk digunakan jika pemuatan gambar gagal (drawablebroken_image
).
imgView.load(imgUri) {
placeholder(R.drawable.loading_animation)
error(R.drawable.ic_broken_image)
}
- Metode
bindImage()
lengkap kini terlihat seperti ini:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
imgView.load(imgUri) {
placeholder(R.drawable.loading_animation)
error(R.drawable.ic_broken_image)
}
}
}
- Jalankan aplikasi. Bergantung pada kecepatan koneksi jaringan, Anda mungkin melihat gambar pemuatan secara cepat sebagai download Glide dan menampilkan gambar properti. Namun, Anda tidak akan melihat ikon gambar yang rusak, meskipun jaringan dinonaktifkan—Anda akan memperbaikinya di tugas terakhir codelab.
- Tampilkan perubahan sementara yang Anda buat di
overview/OverviewFragment.kt
. Pada metodeonCreateview()
, hapus tanda komentar pada baris yang meng-inflateFragmentOverviewBinding
. Menghapus atau mengomentari baris yang meng-inflateGridViewIteMBinding
.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
Aplikasi Anda sekarang memuat foto Mars dari internet. Dengan menggunakan data dari item daftar MarsPhoto
pertama, Anda telah membuat properti LiveData
di ViewModel
, dan telah menggunakan URL gambar dari data foto Mars tersebut untuk mengisi ImageView
. Namun, tujuannya adalah agar aplikasi Anda menampilkan petak gambar, sehingga dalam tugas ini Anda akan menggunakan RecyclerView
dengan pengelola tata letak Petak untuk menampilkan petak gambar.
Mengupdate ViewModel
Pada tugas sebelumnya, di OverviewViewModel
, Anda telah menambahkan objek LiveData
yang disebut _photos
yang menyimpan satu objek MarsPhoto
—objek pertama dalam daftar respons dari layanan web. Pada langkah ini, Anda akan mengubah LiveData
ini untuk menyimpan seluruh daftar objek MarsPhoto
.
- Buka
overview/OverviewViewModel.kt
. - Ubah jenis
_photos
menjadi daftar objekMarsPhoto
.
private val _photos = MutableLiveData<List<MarsPhoto>>()
- Ganti juga jenis properti pendukung
photos
ke jenisList<MarsPhoto>
:
val photos: LiveData<List<MarsPhoto>> = _photos
- Scroll ke bawah ke blok
try {}
di dalam metodegetMarsPhotos()
.MarsApi.
retrofitService
.getPhotos()
menampilkan daftar objek MarsPhoto
, Anda cukup menetapkannya ke _photos.value
.
_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = "Success: Mars properties retrieved"
- Seluruh blok
try/catch
sekarang terlihat seperti ini:
try {
_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = "Success: Mars properties retrieved"
} catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
Tata Letak Petak
GridLayoutManager
untuk RecyclerView
menata letak data sebagai petak yang dapat di-scroll, seperti yang ditampilkan di bawah ini.
Dari perspektif desain, Tata Letak Petak paling baik untuk daftar yang dapat ditampilkan sebagai ikon atau gambar, seperti daftar dalam aplikasi penjelajahan foto Mars Anda.
Cara tata letak Petak menata letak item
Tata letak petak mengatur item dalam petak baris dan kolom. Dengan asumsi scrolling vertikal, secara default, setiap item dalam baris memerlukan satu "span". Sebuah item dapat menempati beberapa span. Dalam kasus di bawah, satu span sama dengan lebar satu kolom yaitu 3.
Dalam dua contoh yang ditampilkan di bawah, setiap baris terdiri dari tiga span. Secara default, GridLayoutManager
menata letak setiap item dalam satu span hingga jumlah span, yang Anda tentukan. Saat mencapai jumlah span, span ini akan dibulatkan ke baris berikutnya.
Menambahkan Recyclerview
Pada langkah ini Anda akan mengubah tata letak aplikasi untuk menggunakan tampilan recycler dengan tata letak petak, bukan satu tampilan gambar.
- Buka
layout/gridview_item.xml
. Hapus variabel dataviewModel
. - Di dalam tag
<data>
, tambahkan variabelphoto
dari jenisMarsPhoto
berikut.
<data>
<variable
name="photo"
type="com.example.android.marsphotos.network.MarsPhoto" />
</data>
- Di
<ImageView>
, ubah atributapp:imageUrl
untuk merujuk ke URL gambar di objekMarsPhoto
. Perubahan ini mengurungkan perubahan sementara yang Anda buat di tugas sebelumnya.
app:imageUrl="@{photo.imgSrcUrl}"
- Buka
layout/fragment_overview.xml
Hapus seluruh elemen<TextView>
. - Sebagai gantinya, tambahkan elemen
<RecyclerView>
berikut. Setel ID ke atributphotos_grid
,width
, danheight
ke0dp
, sehingga mengisiConstraintLayout
induk. Anda akan menggunakan tata letak Petak, jadi setel atributlayoutManager
keandroidx.recyclerview.widget.GridLayoutManager
. SetelspanCount
ke2
sehingga Anda akan memiliki dua kolom.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2" />
- Untuk melihat pratinjau tampilan kode di atas dalam tampilan Design, gunakan
tools:itemCount
untuk menetapkan jumlah item yang ditampilkan dalam tata letak ke16
. AtributitemCount
menentukan jumlah item yang harus dirender editor tata letak di jendela Pratinjau. Setel tata letak item daftar kegrid_view_item
menggunakantools:listitem
.
<androidx.recyclerview.widget.RecyclerView
...
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
- Beralih ke tampilan Design, Anda akan melihat pratinjau seperti screenshot berikut. Ini tidak terlihat seperti foto Mars, tapi ini akan menunjukkan tampilan tata letak petak recyclerview Anda. Pratinjau menggunakan padding dan tata letak
grid_view_item
untuk setiap item petak tunggalrecyclerview
.
- Menurut Panduan desain material, Anda harus memiliki
8dp
ruang di bagian atas, bawah, dan sisi daftar, serta4dp
ruang di antara item. Anda dapat melakukannya dengan kombinasi padding di tata letakfragment_overview.xml
dan di tata letakgridview_item.xml
.
- Buka
layout/gridview_item.xml
. Perhatikan atributpadding
, Anda sudah memiliki2dp
padding antara bagian luar item dan konten. Ini akan memberi kami4dp
ruang di antara konten item, dan2dp
di sepanjang tepi luar, yang berarti kami membutuhkan6dp
padding tambahan di tepi luar agar cocok dengan panduan desain. - Kembali ke
layout/fragment_overview.xml
Tambahkan6dp
padding untukRecyclerView
, sehingga Anda akan memiliki8dp
di bagian luar dan4dp
di bagian dalam sebagai panduan.
<androidx.recyclerview.widget.RecyclerView
...
android:padding="6dp"
... />
- Elemen
<RecyclerView>
lengkap akan terlihat seperti berikut.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
Menambahkan adaptor petak foto
Sekarang tata letak fragment_overview
memiliki RecyclerView
dengan tata letak petak. Pada langkah ini, Anda akan mem-binding data yang diambil dari server web ke RecyclerView
melalui adaptor RecyclerView
.
ListAdapter (Refresher)
ListAdapter
adalah subclass dari class RecyclerView.Adapter
untuk menyajikan data Daftar dalam RecyclerView
, termasuk perbedaan komputasi antara Daftar pada thread latar belakang.
Dalam aplikasi ini, Anda akan menggunakan implementasi DiffUtil
di ListAdapter.
Keuntungan menggunakan DiffUtil
adalah setiap kali beberapa item di RecyclerView
ditambahkan, dihapus, atau diubah, seluruh daftar tidak disegarkan. Hanya item yang telah diubah yang disegarkan.
Menambahkan ListAdapter
ke aplikasi Anda.
- Pada paket
overview
, buat class Kotlin baru bernamaPhotoGridAdapter.kt
. - Perluas class
PhotoGridAdapter
dariListAdapter
dengan parameter konstruktor yang ditampilkan di bawah ini. ClassPhotoGridAdapter
memperluasListAdapter
, yang konstruktornya memerlukan jenis item daftar, holder tampilan, dan implementasiDiffUtil.ItemCallback
.
class PhotoGridAdapter : ListAdapter<MarsPhoto,
PhotoGridAdapter.MarsPhotoViewHolder>(DiffCallback) {
}
Impor class androidx.recyclerview.widget.ListAdapter
dan com.example.android.marsphoto.network.MarsPhoto
bila diminta. Pada langkah-langkah berikut, Anda akan menerapkan implementasi lain yang hilang dari konstruktor ini, yang menghasilkan error.
- Untuk mengatasi error di atas, Anda akan menambahkan metode yang diperlukan pada langkah ini dan menerapkannya nanti dalam tugas ini. Klik class
PhotoGridAdapter
, klik bulb merah, dari menu drop-down, pilih Terapkan anggota. Pada pop-up yang ditampilkan, pilih metodeListAdapter
,onCreateViewHolder()
danonBindViewHolder()
. Android Studio akan tetap menampilkan error yang akan Anda perbaiki di akhir tugas ini.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPhotoViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPhotoViewHolder, position: Int) {
TODO("Not yet implemented")
}
Untuk mengimplementasikan metode onCreateViewHolder
dan onBindViewHolder
, Anda memerlukan MarsPhotoViewHolder
, yang akan ditambahkan pada langkah berikutnya.
- Di dalam
PhotoGridAdapter
, tambahkan definisi class internal untukMarsPhotoViewHolder
, yang memperluasRecyclerView.ViewHolder
. Anda memerlukan variabelGridViewItemBinding
untuk mem-bindingMarsPhoto
ke tata letak, jadi teruskan variabel ke dalamMarsPhotoViewHolder
. ClassViewHolder
dasar memerlukan tampilan dalam konstruktornya, Anda meneruskannya dengan tampilan root binding.
class MarsPhotoViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
Impor androidx.recyclerview.widget.RecyclerView
dan com.example.android.marsrealestate.databinding.GridViewItemBinding
bila diminta.
- Di
MarsPhotoViewHolder
, buat metodebind()
yang menggunakan objekMarsPhoto
sebagai argumen dan menyetelbinding.property
ke objek tersebut. PanggilexecutePendingBindings()
setelah menetapkan properti, yang menyebabkan update segera dijalankan.
fun bind(MarsPhoto: MarsPhoto) {
binding.photo = MarsPhoto
binding.executePendingBindings()
}
- Masih di dalam class
PhotoGridAdapter
dionCreateViewHolder()
, hapus TODO dan tambahkan baris yang ditunjukkan di bawah. MetodeonCreateViewHolder()
perlu menampilkanMarsPhotoViewHolder
baru, yang dibuat dengan meng-inflateGridViewItemBinding
dan menggunakanLayoutInflater
dari konteks indukViewGroup
.
return MarsPhotoViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
Impor android.view.LayoutInflater
bila diminta.
- Dalam metode
onBindViewHolder()
, hapus TODO dan tambahkan baris yang ditampilkan di bawah ini. Di sini, panggilgetItem()
untuk mendapatkan objekMarsPhoto
yang terkait dengan posisiRecyclerView
saat ini, lalu teruskan properti tersebut ke metodebind()
diMarsPhotoViewHolder
.
val marsPhoto = getItem(position)
holder.bind(marsPhoto)
- Di dalam
PhotoGridAdapter
, tambahkan definisi objek pendamping untukDiffCallback
, seperti yang ditunjukkan di bawah ini.
ObjekDiffCallback
memperluasDiffUtil.ItemCallback
dengan jenis objek umum yang ingin Anda bandingkan—MarsPhoto
. Anda akan membandingkan dua objek foto Mars dalam penerapan ini.
companion object DiffCallback : DiffUtil.ItemCallback<MarsPhoto>() {
}
Impor androidx.recyclerview.widget.DiffUtil
bila diminta.
- Tekan bulb merah untuk mengimplementasikan metode pembanding untuk objek
DiffCallback
, yaituareItemsTheSame()
danareContentsTheSame()
.
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
TODO("Not yet implemented")
}
override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
TODO("Not yet implemented") }
- Dalam metode
areItemsTheSame()
, hapusTODO
. Metode ini dipanggil olehDiffUtil
untuk menentukan apakah dua objek mewakili Item yang sama.DiffUtil
menggunakan metode ini untuk mencari tahu apakah objekMarsPhoto
yang baru sama dengan objekMarsPhoto
lama. ID setiap item(MarsPhoto
objek) bersifat unik. Bandingkan IDoldItem
dannewItem
, lalu tampilkan hasilnya.
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
return oldItem.id == newItem.id
}
- Di
areContentsTheSame()
, hapusTODO
. Metode ini dipanggil olehDiffUtil
saat ingin memeriksa apakah dua item memiliki data yang sama. Data penting di MarsPhoto adalah URL gambar. Bandingkan URLoldItem
dannewItem
, lalu tampilkan hasilnya.
override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
return oldItem.imgSrcUrl == newItem.imgSrcUrl
}
Pastikan Anda dapat mengompilasi dan menjalankan aplikasi tanpa error, tetapi emulator menampilkan layar kosong. Anda telah menyiapkan recyclerview, namun tidak ada data yang diteruskan ke sana, yang akan Anda terapkan pada langkah berikutnya.
Tambahkan adaptor binding dan sambungkan bagian-bagiannya
Pada langkah ini, Anda akan menggunakan BindingAdapter
untuk menginisialisasi PhotoGridAdapter
dengan daftar objek MarsPhoto
. Menggunakan BindingAdapter
untuk menyetel data RecyclerView
menyebabkan data binding secara otomatis mengamati LiveData
untuk daftar objek MarsPhoto
. Kemudian, adaptor binding dipanggil secara otomatis saat daftar MarsPhoto
berubah.
- Buka
BindingAdapters.kt
. - Di akhir file, tambahkan metode
bindRecyclerView()
yang menggunakanRecyclerView
dan daftar objekMarsPhoto
sebagai argumen. Anotasi bahwa metode dengan@BindingAdapter
dengan atributlistData
.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsPhoto>?) {
}
Impor androidx.recyclerview.widget.RecyclerView
dan com.example.android.marsphotos.network.MarsPhoto
bila diminta.
- Di dalam fungsi
bindRecyclerView()
, transmisirecyclerView.adapter
kePhotoGridAdapter
dan tetapkan ke propertival
baruadapter.
val adapter = recyclerView.adapter as PhotoGridAdapter
- Di akhir fungsi
bindRecyclerView()
, panggiladapter.submitList()
dengan data daftar foto Mars. Ini akan memberi tahuRecyclerView
saat daftar baru tersedia.
adapter.submitList(data)
Impor com.example.android.marsrealestate.overview.PhotoGridAdapter
bila diminta.
- Adaptor binding
bindRecyclerView
lengkap akan terlihat seperti ini:
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsPhoto>?) {
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
}
- Untuk menyambungkan semuanya, buka
res/layout/fragment_overview.xml
. Tambahkan atributapp:listData
ke elemenRecyclerView
dan tetapkan keviewmodel.photos
menggunakan data binding. Ini mirip dengan apa yang telah Anda lakukan untukImageView
di tugas sebelumnya.
app:listData="@{viewModel.photos}"
- Buka
overview/OverviewFragment.kt
. DionCreateView()
, tepat sebelum pernyataanreturn
, inisialisasi adaptorRecyclerView
dibinding.photosGrid
menjadi objekPhotoGridAdapter
baru.
binding.photosGrid.adapter = PhotoGridAdapter()
- Jalankan aplikasi. Anda akan melihat petak scrolling gambar Mars. Saat Anda scroll untuk melihat gambar baru namun terlihat sedikit aneh. Padding akan tetap berada di bagian atas dan bawah
RecyclerView
saat Anda men-scroll, sehingga tidak akan pernah tampak seperti daftar di-scroll di bawah Panel Tindakan.
- Untuk memperbaikinya, Anda perlu memberi tahu
RecyclerView
agar tidak memangkas konten bagian dalam ke padding menggunakan atribut android:clipToPadding. Tindakan ini akan membuat tampilan scroll di area padding. Kembali kelayout/fragment_overview.xml
. Tambahkan atributandroid:clipToPadding
untukRecyclerView
, tetapkan kefalse
.
<androidx.recyclerview.widget.RecyclerView
...
android:clipToPadding="false"
... />
- Jalankan aplikasi Anda. Perhatikan bahwa aplikasi juga menampilkan ikon kemajuan pemuatan sebelum menampilkan gambar itu sendiri, seperti yang diharapkan. Ini adalah gambar pemuatan placeholder yang Anda teruskan ke library gambar Coil.
- Saat aplikasi berjalan, aktifkan mode pesawat. Scroll gambar di emulator. Gambar yang belum dimuat akan muncul sebagai ikon gambar yang rusak. Ini adalah drawable gambar yang Anda teruskan ke library gambar Coil untuk ditampilkan jika terjadi error jaringan atau gambar tidak bisa diambil.
Selamat, Anda hampir selesai. Pada tugas terakhir berikutnya, Anda akan meningkatkan pengalaman pengguna dengan menambahkan lebih banyak penanganan error untuk aplikasi.
Aplikasi MarsPhotos menampilkan ikon gambar rusak saat gambar tidak dapat diambil. Namun saat tidak ada jaringan, aplikasi akan menampilkan layar kosong. Anda akan memverifikasi layar kosong di langkah berikutnya.
- Aktifkan mode pesawat di perangkat atau emulator. Jalankan aplikasi dari Android Studio. Perhatikan layar kosong.
Ini bukan pengalaman pengguna yang baik. Dalam tugas ini, Anda akan menambahkan penanganan error dasar, untuk memberikan ide yang lebih baik kepada pengguna tentang apa yang terjadi. Jika internet tidak tersedia, aplikasi akan menampilkan ikon error koneksi dan saat aplikasi mengambil daftar MarsPhoto
, aplikasi akan menampilkan animasi pemuatan.
Menambahkan status ke ViewModel
Dalam tugas ini, Anda akan membuat properti di OverviewViewModel
untuk mewakili status permintaan web. Ada tiga status untuk dipertimbangkan—memuat, berhasil, dan kegagalan. Status pemuatan terjadi saat Anda menunggu data. Status keberhasilan adalah saat kami berhasil mengambil data dari webservice. Status kegagalan menunjukkan adanya error jaringan atau koneksi.
Class Enum di Kotlin
Untuk mewakili ketiga status ini dalam aplikasi, Anda akan menggunakan enum
. enum
adalah kependekan dari enumerasi, yang berarti daftar yang diurutkan dari semua item dalam koleksi. Setiap konstanta enum
adalah objek dari class enum
.
Di Kotlin, enum
adalah jenis data yang dapat menyimpan kumpulan konstanta. Semua itu ditentukan dengan menambahkan kata kunci enum
di depan definisi class seperti yang ditunjukkan di bawah ini. Konstanta Enum dipisahkan dengan koma.
Definisi:
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
Penggunaan:
var direction = Direction.NORTH;
Seperti yang ditunjukkan di atas, objek enum
dapat direferensikan menggunakan nama class yang diikuti dengan operator titik (.) dan nama konstanta.
Tambahkan definisi class enum dengan nilai status di Viewmodel.
- Buka
overview/OverviewViewModel.kt
. Di bagian atas file (setelah impor, sebelum definisi class), tambahkanenum
untuk mewakili semua status yang tersedia:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- Scroll ke definisi properti
_status
danstatus
, mengubah jenis dariString
menjadiMarsApiStatus. MarsApiStatus
adalah class enum yang Anda tentukan di langkah sebelumnya.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus> = _status
- Pada metode
getMarsPhotos()
, ubah string"Success: ..."
menjadi statusMarsApiStatus.DONE
, dan string"Failure..."
menjadiMarsApiStatus.ERROR
.
try {
_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = MarsApiStatus.DONE
} catch (e: Exception)
_status.value = MarsApiStatus.ERROR
}
- Setel status ke
MarsApiStatus.LOADING
di atas bloktry {}
. Ini adalah status awal saat coroutine berjalan dan Anda sedang menunggu data. BlokviewModelScope.launch
{}
lengkap sekarang akan terlihat seperti ini:
viewModelScope.launch {
_status.value = MarsApiStatus.LOADING
try {
_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = MarsApiStatus.DONE
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
}
- Setelah status error di blok
catch {}
, setel_photos
ke daftar kosong. Ini akan menghapus tampilan Recycler.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_photos.value = listOf()
}
- Metode
getMarsPhotos()
yang lengkap akan terlihat seperti ini:
private fun getMarsPhotos() {
viewModelScope.launch {
_status.value = MarsApiStatus.LOADING
try {
_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = MarsApiStatus.DONE
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_photos.value = listOf()
}
}
}
Anda telah menentukan status enum untuk status dan menyetel status pemuatan di awal coroutine, menyetel selesai saat aplikasi selesai mengambil data dari server web, dan menetapkan error saat ada pengecualian. Pada tugas berikutnya, Anda akan menggunakan adaptor binding untuk menampilkan ikon yang sesuai.
Menambahkan adaptor binding untuk status ImageView
Anda telah menyiapkan MarsApiStatus
di OverviewViewModel
, menggunakan kumpulan enum
status. Pada langkah ini, Anda akan menampilkannya di aplikasi. Anda menggunakan adaptor Binding untuk ImageView
, guna menampilkan ikon untuk status pemuatan dan error. Saat aplikasi berada dalam status pemuatan atau status error, ImageView
akan terlihat. Saat aplikasi selesai dimuat, ImageView
seharusnya tidak terlihat.
- Buka
BindingAdapters.kt
, scroll ke akhir file untuk menambahkan adaptor lain. Tambahkan adaptor binding baru bernamabindStatus()
yang menggunakan nilaiImageView
danMarsApiStatus
sebagai argumen. Anotasi metode dengan@BindingAdapter
meneruskan atribut khususmarsApiStatus
sebagai parameter.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
Impor com.example.android.marsrealestate.overview.MarsApiStatus
bila diminta.
- Tambahkan blok
when {}
di dalam metodebindStatus()
untuk beralih antar-status.
when (status) {
}
- Di dalam
when {}
, tambahkan kasus untuk status pemuatan (MarsApiStatus.LOADING
). Untuk status ini, setelImageView
menjadi terlihat, dan tetapkan animasi pemuatan. Ini adalah drawable animasi yang sama dengan yang digunakan untuk Coil dalam tugas sebelumnya.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
Impor android.view.View
bila diminta.
- Tambahkan kasus untuk status error, yaitu
MarsApiStatus.ERROR
. Demikian pula dengan apa yang Anda lakukan untuk statusLOADING
, setel statusImageView
menjadi terlihat dan gunakan drawable koneksi-error.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- Tambahkan kasus untuk status selesai, yaitu
MarsApiStatus.DONE
. Di sini Anda memiliki respons yang berhasil, jadi setel keterlihatan statusImageView
menjadiView.
GONE
untuk menyembunyikannya.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
Anda telah menyiapkan adaptor binding untuk tampilan Gambar status, pada langkah berikutnya Anda akan menambahkan tampilan Gambar yang menggunakan adaptor binding baru.
Menambahkan status ImageView
Pada langkah ini, Anda akan menambahkan tampilan Gambar di fragment_overview.xml
yang akan menampilkan status yang Anda tentukan sebelumnya.
- Buka
res/layout/fragment_overview.xml
Di dalamConstraintLayout
, di bawah elemenRecyclerView
, tambahkanImageView
yang ditunjukkan di bawah.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
ImageView
di atas memiliki batasan yang sama dengan RecyclerView
. Namun, lebar dan tinggi menggunakan wrap_content
untuk menengahkan gambar, bukan melebarkan gambar untuk memenuhi tampilan. Perhatikan juga atribut app:marsApiStatus
yang disetel ke viewModel.status
, yang memanggil BindingAdapter
Anda saat properti status di ViewModel
berubah.
- Untuk menguji kode di atas, simulasi error koneksi jaringan dengan mengaktifkan mode pesawat di emulator atau perangkat Anda. Kompilasi dan jalankan aplikasi, dan perhatikan bahwa gambar error muncul:
- Ketuk tombol Kembali untuk menutup aplikasi dan nonaktifkan mode pesawat. Gunakan layar terbaru untuk menampilkan aplikasi. Bergantung pada kecepatan koneksi jaringan, Anda mungkin melihat indikator lingkaran berputar yang sangat singkat saat aplikasi menanyakan layanan web sebelum gambar mulai dimuat.
Selamat karena telah menyelesaikan codelab ini dan membuat aplikasi MarsPhotos! Kini saatnya menampilkan aplikasi Anda dengan gambar Mars nyata bersama keluarga dan teman Anda.
Kode solusi untuk codelab ini ada dalam project yang ditampilkan di bawah. Gunakan cabang utama untuk mengambil atau mendownload kode.
Guna mendapatkan kode untuk 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 membuat dan menjalankan aplikasi. Pastikan aplikasi berfungsi seperti yang diharapkan.
- Cari file project di jendela fitur Project untuk melihat cara aplikasi diterapkan.
- Library Coil menyederhanakan proses pengelolaan gambar, seperti download, buffer, decode, dan cache gambar di aplikasi Anda.
- Adaptor Binding adalah metode ekstensi yang berada di antara tampilan dan data yang mem-binding tampilan. Adaptor binding menyediakan perilaku kustom saat data berubah, misalnya, untuk memanggil Coil untuk memuat gambar dari URL ke dalam
ImageView
. - Adaptor binding adalah metode ekstensi yang dianotasi dengan anotasi
@BindingAdapter
. - Untuk menampilkan petak gambar, gunakan
RecyclerView
denganGridLayoutManager
. - Untuk memperbarui daftar properti saat berubah, gunakan adaptor binding antara
RecyclerView
dan tata letak.
Dokumentasi developer Android:
Lainnya: