Memuat dan menampilkan gambar dari Internet

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, dan DiffUtil.

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.

1b33675b009bee15.png

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

  1. Buka aplikasi MarsPhotos dari codelab sebelumnya.
  2. Jalankan aplikasi untuk melihat fungsinya. (Ini menunjukkan jumlah total foto Mars yang diambil).
  3. Buka build.gradle (Module: app).
  4. 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.

  1. Library Coil dihosting dan tersedia di repositori mavenCentral(). Di build.gradle (Project: MarsPhotos), tambahkan mavenCentral() di blok repositories atas.
repositories {
   google()
   jcenter()
   mavenCentral()
}
  1. 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.

  1. Buka overview/OverviewViewModel.kt. Tepat di bawah deklarasi properti _status, tambahkan properti baru yang dapat berubah yang disebut _photos, dari jenis MutableLiveData yang dapat menyimpan objek MarsPhoto tunggal.
private val _photos = MutableLiveData<MarsPhoto>()

Impor com.example.android.marsphotos.network.MarsPhoto saat diminta.

  1. Tepat di bawah deklarasi _photos, tambahkan kolom dukungan publik yang disebut photos dari jenisnya, LiveData<MarsPhoto>.
val photos: LiveData<MarsPhoto> = _photos
  1. Pada metode getMarsPhotos(), di dalam blok try{}, temukan baris yang menyetel data yang diambil dari layanan web menjadi listResult.
try {
   val listResult = MarsApi.retrofitService.getPhotos()
   ...
}
  1. Tetapkan foto Mars pertama yang diambil ke variabel baru _photos. Ubah listResult menjadi _photos.value. Tetapkan url foto pertama pada indeks 0. Ini akan memunculkan error, Anda akan memperbaikinya nanti.
try {
   _photos.value = MarsApi.retrofitService.getPhotos()[0]
   ...
}
  1. Di baris berikutnya, update status.value menjadi yang berikut. Gunakan data dari properti baru, bukan listResult. Tampilkan URL gambar pertama dari Daftar foto .
try {
   ...
   _status.value = "   First Mars image URL : ${_photos.value!!.imgSrcUrl}"

}
  1. Blok lengkap try{} sekarang terlihat seperti ini:
try {
   _photos.value = MarsApi.retrofitService.getPhotos()[0]
   _status.value = "   First Mars image URL : ${_photos.value!!.imgSrcUrl}"
}
  1. Jalankan aplikasi. TextView sekarang menampilkan URL foto Mars pertama. Semua yang telah Anda lakukan sejauh ini adalah menyiapkan ViewModel dan LiveData untuk URL tersebut.

b8ac93805b69b03a.png

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

  1. Dalam paket com.example.android.marsphotos, buat file Kotlin bernama BindingAdapters. File ini akan menyimpan adaptor binding yang Anda gunakan di seluruh aplikasi.

a04afbd6ae8ccfcd.png

  1. Di BindingAdapters.kt. Buat fungsi bindImage() yang menggunakan parameter ImageView dan String.
fun bindImage(imgView: ImageView, imgUrl: String?) {

}

Impor android.widget.ImageView bila diminta.

  1. Anotasi fungsi dengan @BindingAdapter. Anotasi @BindingAdapter memberi tahu data binding untuk mengeksekusi adaptor binding ini jika item Tampilan memiliki atribut imageUrl.
@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.

  1. Di dalam fungsi bindImage(), tambahkan blok let{} ke argumen imageURL, menggunakan operator panggilan aman.
imgUrl?.let {
}
  1. Di dalam blok let{}, tambahkan baris berikut untuk mengonversi string URL menjadi objek Uri menggunakan metode toUri(). Untuk menggunakan skema HTTPS, tambahkan buildUpon.scheme("https") ke builder toUri. Panggil build() untuk membuat objek.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()

Impor androidx.core.net.toUri bila diminta.

  1. Di dalam blok let{}, setelah deklarasi imgUri, gunakan load(){} dari Coil, untuk memuat gambar dari objek imgUri ke imgView.
imgView.load(imgUri) {
}

Impor coil.load bila diminta.

  1. 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.

  1. Buka res/layout/grid_view_item.xml
  2. Di atas elemen <ImageView>, tambahkan elemen <data> untuk data binding, dan binding ke class OverviewViewModel:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsphotos.overview.OverviewViewModel" />
</data>
  1. Tambahkan atribut app:imageUrl ke elemen ImageView untuk menggunakan adaptor binding pemuatan gambar baru. Ingatlah bahwa photos berisi daftar MarsPhotos yang diambil dari server. Tetapkan URL entri pertama ke atribut imageUrl.
    <ImageView
        android:id="@+id/mars_image"
        ...
        app:imageUrl="@{viewModel.photos.imgSrcUrl}"
        ... />
  1. Buka overview/OverviewFragment.kt. Dalam metode onCreateView(), komentari baris yang meng-inflate class FragmentOverviewBinding 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)
  1. Gunakan grid_view_item.xml, bukan fragment_overview.xml. Tambahkan baris berikut untuk meng-inflate class GridViewItemBinding.
val binding = GridViewItemBinding.inflate(inflater)

Impor com.example.android.marsphotos. databinding.GridViewItemBinding bila diminta.

  1. Jalankan aplikasi. Anda sekarang akan melihat satu gambar Mars.

e59b6e849e63ae2b.png

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.

  1. 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 atribut android:tint untuk mewarnai ikon menjadi abu-abu.

467c213c859e1904.png

  1. 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

6c1f87d1c932c762.png

  1. Kembali ke file BindingAdapters.kt. Dalam metode bindImage(), perbarui panggilan ke imgView.load(imgUri) untuk menambahkan lambda trailing sebagai berikut: Kode ini menetapkan placeholder memuat gambar untuk digunakan saat memuat (drawable loading_animation). Kode ini juga menyetel gambar untuk digunakan jika pemuatan gambar gagal (drawable broken_image).
imgView.load(imgUri) {
   placeholder(R.drawable.loading_animation)
   error(R.drawable.ic_broken_image)
}
  1. 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)
        }
    }
}
  1. 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.

80553d5e5c7641de.gif

  1. Tampilkan perubahan sementara yang Anda buat di overview/OverviewFragment.kt. Pada metode onCreateview(), hapus tanda komentar pada baris yang meng-inflate FragmentOverviewBinding. Menghapus atau mengomentari baris yang meng-inflate GridViewIteMBinding.
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.

  1. Buka overview/OverviewViewModel.kt.
  2. Ubah jenis _photos menjadi daftar objek MarsPhoto.
private val _photos = MutableLiveData<List<MarsPhoto>>()
  1. Ganti juga jenis properti pendukung photos ke jenis List<MarsPhoto>:
 val photos: LiveData<List<MarsPhoto>> = _photos
  1. Scroll ke bawah ke blok try {} di dalam metode getMarsPhotos(). MarsApi.retrofitService.getPhotos()

menampilkan daftar objek MarsPhoto, Anda cukup menetapkannya ke _photos.value.

_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = "Success: Mars properties retrieved"
  1. 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.

fcf0fc4b78f8650.png

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.

  1. Buka layout/gridview_item.xml. Hapus variabel data viewModel.
  2. Di dalam tag <data>, tambahkan variabel photo dari jenis MarsPhoto berikut.
<data>
   <variable
       name="photo"
       type="com.example.android.marsphotos.network.MarsPhoto" />
</data>
  1. Di <ImageView>, ubah atribut app:imageUrl untuk merujuk ke URL gambar di objek MarsPhoto. Perubahan ini mengurungkan perubahan sementara yang Anda buat di tugas sebelumnya.
app:imageUrl="@{photo.imgSrcUrl}"
  1. Buka layout/fragment_overview.xml Hapus seluruh elemen <TextView>.
  2. Sebagai gantinya, tambahkan elemen <RecyclerView> berikut. Setel ID ke atribut photos_grid, width, dan height ke 0dp, sehingga mengisi ConstraintLayout induk. Anda akan menggunakan tata letak Petak, jadi setel atribut layoutManager ke androidx.recyclerview.widget.GridLayoutManager. Setel spanCount ke 2 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" />
  1. Untuk melihat pratinjau tampilan kode di atas dalam tampilan Design, gunakan tools:itemCount untuk menetapkan jumlah item yang ditampilkan dalam tata letak ke 16. Atribut itemCount menentukan jumlah item yang harus dirender editor tata letak di jendela Pratinjau. Setel tata letak item daftar ke grid_view_item menggunakan tools:listitem.
<androidx.recyclerview.widget.RecyclerView
            ...
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />
  1. 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 tunggal recyclerview.

20742824367c3952.png

  1. Menurut Panduan desain material, Anda harus memiliki 8dp ruang di bagian atas, bawah, dan sisi daftar, serta 4dp ruang di antara item. Anda dapat melakukannya dengan kombinasi padding di tata letak fragment_overview.xml dan di tata letak gridview_item.xml.

a3561fa85fea7a8f.png

  1. Buka layout/gridview_item.xml. Perhatikan atribut padding, Anda sudah memiliki 2dp padding antara bagian luar item dan konten. Ini akan memberi kami 4dp ruang di antara konten item, dan 2dp di sepanjang tepi luar, yang berarti kami membutuhkan 6dp padding tambahan di tepi luar agar cocok dengan panduan desain.
  2. Kembali ke layout/fragment_overview.xml Tambahkan 6dp padding untuk RecyclerView, sehingga Anda akan memiliki 8dp di bagian luar dan 4dp di bagian dalam sebagai panduan.
<androidx.recyclerview.widget.RecyclerView
            ...
            android:padding="6dp"
            ...  />
  1. 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.

  1. Pada paket overview, buat class Kotlin baru bernama PhotoGridAdapter.kt.
  2. Perluas class PhotoGridAdapter dari ListAdapter dengan parameter konstruktor yang ditampilkan di bawah ini. Class PhotoGridAdapter memperluas ListAdapter, yang konstruktornya memerlukan jenis item daftar, holder tampilan, dan implementasi DiffUtil.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.

  1. 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 metode ListAdapter, onCreateViewHolder() dan onBindViewHolder(). 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.

  1. Di dalam PhotoGridAdapter, tambahkan definisi class internal untuk MarsPhotoViewHolder, yang memperluas RecyclerView.ViewHolder. Anda memerlukan variabel GridViewItemBinding untuk mem-binding MarsPhoto ke tata letak, jadi teruskan variabel ke dalam MarsPhotoViewHolder. Class ViewHolder 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.

  1. Di MarsPhotoViewHolder, buat metode bind() yang menggunakan objek MarsPhoto sebagai argumen dan menyetel binding.property ke objek tersebut. Panggil executePendingBindings() setelah menetapkan properti, yang menyebabkan update segera dijalankan.
fun bind(MarsPhoto: MarsPhoto) {
   binding.photo = MarsPhoto
   binding.executePendingBindings()
}
  1. Masih di dalam class PhotoGridAdapter di onCreateViewHolder(), hapus TODO dan tambahkan baris yang ditunjukkan di bawah. Metode onCreateViewHolder() perlu menampilkan MarsPhotoViewHolder baru, yang dibuat dengan meng-inflate GridViewItemBinding dan menggunakan LayoutInflater dari konteks induk ViewGroup.
   return MarsPhotoViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))

Impor android.view.LayoutInflater bila diminta.

  1. Dalam metode onBindViewHolder(), hapus TODO dan tambahkan baris yang ditampilkan di bawah ini. Di sini, panggil getItem() untuk mendapatkan objek MarsPhoto yang terkait dengan posisi RecyclerView saat ini, lalu teruskan properti tersebut ke metode bind() di MarsPhotoViewHolder.
val marsPhoto = getItem(position)
holder.bind(marsPhoto)
  1. Di dalam PhotoGridAdapter, tambahkan definisi objek pendamping untuk DiffCallback, seperti yang ditunjukkan di bawah ini.
    Objek DiffCallback memperluas DiffUtil.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.

  1. Tekan bulb merah untuk mengimplementasikan metode pembanding untuk objek DiffCallback, yaitu areItemsTheSame() dan areContentsTheSame().
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   TODO("Not yet implemented")
}

override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   TODO("Not yet implemented") }
  1. Dalam metode areItemsTheSame(), hapus TODO. Metode ini dipanggil oleh DiffUtil untuk menentukan apakah dua objek mewakili Item yang sama. DiffUtil menggunakan metode ini untuk mencari tahu apakah objek MarsPhoto yang baru sama dengan objek MarsPhoto lama. ID setiap item(MarsPhoto objek) bersifat unik. Bandingkan ID oldItem dan newItem, lalu tampilkan hasilnya.
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   return oldItem.id == newItem.id
}
  1. Di areContentsTheSame(), hapus TODO. Metode ini dipanggil oleh DiffUtil saat ingin memeriksa apakah dua item memiliki data yang sama. Data penting di MarsPhoto adalah URL gambar. Bandingkan URL oldItem dan newItem, 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.

  1. Buka BindingAdapters.kt.
  2. Di akhir file, tambahkan metode bindRecyclerView() yang menggunakan RecyclerView dan daftar objek MarsPhoto sebagai argumen. Anotasi bahwa metode dengan @BindingAdapter dengan atribut listData.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
    data: List<MarsPhoto>?) {
}

Impor androidx.recyclerview.widget.RecyclerView dan com.example.android.marsphotos.network.MarsPhoto bila diminta.

  1. Di dalam fungsi bindRecyclerView(), transmisi recyclerView.adapter ke PhotoGridAdapter dan tetapkan ke properti val baru adapter.
val adapter = recyclerView.adapter as PhotoGridAdapter
  1. Di akhir fungsi bindRecyclerView(), panggil adapter.submitList() dengan data daftar foto Mars. Ini akan memberi tahu RecyclerView saat daftar baru tersedia.
adapter.submitList(data)

Impor com.example.android.marsrealestate.overview.PhotoGridAdapter bila diminta.

  1. 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)

}
  1. Untuk menyambungkan semuanya, buka res/layout/fragment_overview.xml. Tambahkan atribut app:listData ke elemen RecyclerView dan tetapkan ke viewmodel.photos menggunakan data binding. Ini mirip dengan apa yang telah Anda lakukan untuk ImageView di tugas sebelumnya.
app:listData="@{viewModel.photos}"
  1. Buka overview/OverviewFragment.kt. Di onCreateView(), tepat sebelum pernyataan return, inisialisasi adaptor RecyclerView di binding.photosGrid menjadi objek PhotoGridAdapter baru.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. 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.

5d03641aa1589842.png

  1. 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 ke layout/fragment_overview.xml. Tambahkan atribut android:clipToPadding untuk RecyclerView, tetapkan ke false.
<androidx.recyclerview.widget.RecyclerView
            ...
            android:clipToPadding="false"
            ...  />
  1. 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.

3128b84aa22ef97e.png

  1. 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.

28d2cbba564f35ff.png

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.

  1. Aktifkan mode pesawat di perangkat atau emulator. Jalankan aplikasi dari Android Studio. Perhatikan layar kosong.

492011786c2dd7f7.png

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.

  1. Buka overview/OverviewViewModel.kt. Di bagian atas file (setelah impor, sebelum definisi class), tambahkan enum untuk mewakili semua status yang tersedia:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Scroll ke definisi properti _status dan status, mengubah jenis dari String menjadi MarsApiStatus. MarsApiStatus adalah class enum yang Anda tentukan di langkah sebelumnya.
private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus> = _status
  1. Pada metode getMarsPhotos(), ubah string "Success: ..." menjadi status MarsApiStatus.DONE, dan string "Failure..." menjadi MarsApiStatus.ERROR.
try {
    _photos.value = MarsApi.retrofitService.getPhotos()
    _status.value = MarsApiStatus.DONE
} catch (e: Exception)
     _status.value = MarsApiStatus.ERROR
}
  1. Setel status ke MarsApiStatus.LOADING di atas blok try {}. Ini adalah status awal saat coroutine berjalan dan Anda sedang menunggu data. Blok viewModelScope.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
            }
        }
  1. 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()
}
  1. 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.

  1. Buka BindingAdapters.kt, scroll ke akhir file untuk menambahkan adaptor lain. Tambahkan adaptor binding baru bernama bindStatus() yang menggunakan nilai ImageView dan MarsApiStatus sebagai argumen. Anotasi metode dengan @BindingAdapter meneruskan atribut khusus marsApiStatus sebagai parameter.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
          status: MarsApiStatus?) {
}

Impor com.example.android.marsrealestate.overview.MarsApiStatus bila diminta.

  1. Tambahkan blok when {} di dalam metode bindStatus() untuk beralih antar-status.
when (status) {

}
  1. Di dalam when {}, tambahkan kasus untuk status pemuatan (MarsApiStatus.LOADING). Untuk status ini, setel ImageView 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.

  1. Tambahkan kasus untuk status error, yaitu MarsApiStatus.ERROR. Demikian pula dengan apa yang Anda lakukan untuk status LOADING, setel status ImageView menjadi terlihat dan gunakan drawable koneksi-error.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Tambahkan kasus untuk status selesai, yaitu MarsApiStatus.DONE. Di sini Anda memiliki respons yang berhasil, jadi setel keterlihatan status ImageView menjadi View.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.

  1. Buka res/layout/fragment_overview.xml Di dalam ConstraintLayout, di bawah elemen RecyclerView, tambahkan ImageView 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.

  1. 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:

a91ddb1c89f2efec.png

  1. 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

  1. Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
  2. Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
  2. Temukan file di komputer Anda (mungkin di folder Downloads).
  3. Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.

Membuka project di Android Studio

  1. Mulai Android Studio.
  2. Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
  2. Klik dua kali pada folder project tersebut.
  3. Tunggu Android Studio membuka project.
  4. Klik tombol Run j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg untuk membuat dan menjalankan aplikasi. Pastikan aplikasi berfungsi seperti yang diharapkan.
  5. 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 dengan GridLayoutManager.
  • Untuk memperbarui daftar properti saat berubah, gunakan adaptor binding antara RecyclerView dan tata letak.

Dokumentasi developer Android:

Lainnya: