Mendapatkan data dari internet

1. Sebelum memulai

Sebagian besar aplikasi Android di pasar terhubung ke internet untuk melakukan operasi jaringan, seperti mengambil email, pesan, atau informasi lainnya dari server backend. Gmail, YouTube, dan Google Foto adalah contoh aplikasi yang terhubung ke internet untuk menampilkan data pengguna.

Dalam codelab ini, Anda akan menggunakan library open source dan berbasis komunitas untuk membangun lapisan data dan mendapatkan data dari server backend. Hal ini sangat menyederhanakan pengambilan data dan juga membantu aplikasi mengikuti praktik terbaik Android, seperti melakukan operasi di thread latar belakang. Anda juga akan menampilkan pesan error jika internet lambat atau tidak tersedia, sehingga pengguna akan tetap mendapatkan informasi terkait masalah konektivitas jaringan.

Prasyarat

  • Pengetahuan dasar tentang cara membuat fungsi Composable.
  • Pengetahuan dasar tentang cara menggunakan komponen arsitektur Android ViewModel.
  • Pengetahuan dasar tentang cara menggunakan coroutine untuk tugas yang berjalan lama.
  • Pengetahuan dasar tentang cara menambahkan dependensi di build.gradle.kts.

Yang akan Anda pelajari

Yang akan Anda lakukan

  • Memodifikasi aplikasi awal untuk membuat permintaan API layanan web dan menangani respons.
  • Menerapkan lapisan data untuk aplikasi Anda menggunakan library Retrofit.
  • Menguraikan respons JSON dari layanan web ke daftar objek data aplikasi Anda dengan library kotlinx.serialization, dan melampirkannya ke status UI.
  • Menggunakan dukungan Retrofit untuk coroutine guna menyederhanakan kode.

Yang Anda perlukan

  • Komputer dengan Android Studio
  • Kode awal untuk aplikasi Mars Photos

2. Ringkasan aplikasi

Anda bekerja dengan aplikasi bernama Mars Photos, yang menampilkan gambar permukaan Mars. Aplikasi ini terhubung ke layanan web untuk mengambil dan menampilkan foto Mars. Gambar-gambar tersebut adalah foto kehidupan nyata dari Mars yang diambil dari penjelajah Mars NASA. Gambar berikut adalah screenshot aplikasi final, yang berisi petak gambar.

68f4ff12cc1e2d81.png

Versi aplikasi yang Anda buat di codelab ini tidak akan memiliki banyak flash visual. Codelab ini berfokus pada bagian lapisan data aplikasi untuk terhubung ke internet dan mendownload data properti mentah menggunakan layanan web. Untuk memastikan aplikasi mengambil dan menguraikan data ini dengan benar, Anda dapat mencetak jumlah foto yang diterima dari server backend dalam composable Text.

a59e55909b6e9213.png

3. Jelajahi aplikasi awal Mars Photos

Mendownload kode awal

Untuk memulai, download kode awal:

Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git
$ cd basic-android-kotlin-compose-training-mars-photos
$ git checkout starter

Anda dapat menjelajahi kode di repositori GitHub Mars Photos.

Menjalankan kode awal

  1. Buka project yang telah didownload di Android Studio. Nama folder project adalah basic-android-kotlin-compose-training-mars-photos.
  2. Di panel Android, luaskan app > java. Perhatikan bahwa aplikasi memiliki folder paket bernama ui. Ini adalah lapisan UI aplikasi.

d5102d14c37fc433.png

  1. Jalankan aplikasi. Saat mengompilasi dan menjalankan aplikasi, Anda akan melihat layar berikut dengan teks placeholder di tengah. Di akhir codelab ini, Anda akan memperbarui teks placeholder ini dengan jumlah foto yang diambil.

95328ffbc9d7104b.png

Panduan kode awal

Dalam tugas ini, Anda akan memahami struktur project. Daftar berikut memberikan panduan terkait file dan folder penting dalam project.

ui\MarsPhotosApp.kt:

  • File ini berisi composable, MarsPhotosApp, yang menampilkan konten di layar, seperti panel aplikasi atas dan composable HomeScreen. Teks placeholder di langkah sebelumnya ditampilkan di composable ini.
  • Dalam codelab berikutnya, composable ini menampilkan data yang diterima dari server backend foto Mars.

screens\MarsViewModel.kt:

  • File ini adalah model tampilan yang sesuai untuk MarsPhotosApp.
  • Class ini berisi properti MutableState bernama marsUiState. Memperbarui nilai properti ini akan memperbarui teks placeholder yang ditampilkan di layar.
  • Metode getMarsPhotos() memperbarui respons placeholder. Selanjutnya di codelab, Anda menggunakan metode ini untuk menampilkan data yang diambil dari server. Tujuan codelab ini adalah memperbarui MutableState dalam ViewModel menggunakan data nyata yang Anda dapatkan dari internet.

screens\HomeScreen.kt:

  • File ini berisi composable HomeScreen dan ResultScreen. ResultScreen memiliki tata letak Box sederhana yang menampilkan nilai marsUiState dalam composable Text.

MainActivity.kt:

  • Satu-satunya tugas untuk aktivitas ini adalah memuat ViewModel dan menampilkan composable MarsPhotosApp.

4. Pengantar layanan web

Dalam codelab ini, Anda membuat lapisan untuk layanan jaringan yang berkomunikasi dengan server backend dan mengambil data yang diperlukan. Anda menggunakan library pihak ketiga, yang disebut Retrofit, untuk menerapkan tugas ini. Anda akan mempelajari hal ini lebih lanjut nanti. ViewModel berkomunikasi dengan lapisan data, dan aplikasi lainnya bersifat transparan untuk penerapan ini.

3fe849669bd308da.png

MarsViewModel bertanggung jawab membuat panggilan jaringan untuk mendapatkan data foto Mars. Di ViewModel, Anda menggunakan MutableState untuk mengupdate UI aplikasi saat data berubah.

5. Layanan web dan Retrofit

Data foto Mars disimpan di server web. Untuk memasukkan data ini ke dalam aplikasi, Anda harus membuat koneksi dan berkomunikasi dengan server di internet.

301162f0dca12fcf.png

9d5543c4c7e85e9f.png

Sebagian besar server web saat ini menjalankan layanan web menggunakan arsitektur web stateless umum yang dikenal sebagai REST, yang merupakan kependekan dari REpresentational State Transfer. Layanan web yang menawarkan arsitektur ini dikenal sebagai layanan RESTful.

Permintaan dibuat untuk layanan web RESTful dengan cara standar, melalui Uniform Resource Identifiers (URI). URI mengidentifikasi resource di server berdasarkan nama, tanpa menyiratkan lokasi atau cara mengaksesnya. Misalnya, dalam aplikasi untuk pelajaran ini, Anda akan mengambil URL gambar menggunakan URI server berikut. (Server ini menghosting real estate Mars dan foto Mars):

android-kotlin-fun-mars-server.appspot.com

URL (Uniform Resource Locator) adalah subset URI yang menentukan lokasi resource dan mekanisme untuk mengambilnya.

Contoh:

URL berikut berisi daftar properti real estate yang tersedia di Mars:

https://android-kotlin-fun-mars-server.appspot.com/realestate

URL berikut mendapatkan daftar foto Mars:

https://android-kotlin-fun-mars-server.appspot.com/photos

URL ini merujuk ke resource yang diidentifikasi, seperti /realestate atau /photos, yang dapat diperoleh melalui Hypertext Transfer Protocol (http:) dari jaringan. Anda menggunakan endpoint /photos dalam codelab ini. Endpoint adalah URL yang memungkinkan Anda mengakses layanan web yang berjalan di server.

Permintaan layanan web

Setiap permintaan layanan web berisi URI dan ditransfer ke server menggunakan protokol HTTP yang sama dengan yang digunakan oleh browser web, seperti Chrome. Permintaan HTTP berisi operasi untuk memberi tahu server apa yang harus dilakukan.

Operasi HTTP umum meliputi:

  • GET untuk mengambil data server.
  • POST untuk membuat data baru di server.
  • PUT untuk memperbarui data yang ada di server.
  • DELETE untuk menghapus data dari server.

Aplikasi Anda membuat permintaan GET HTTP ke server untuk mendapatkan informasi foto Mars, lalu server menampilkan respons ke aplikasi Anda, termasuk URL gambar.

5bbeef4ded3e84cf.png

83e8a6eb79249ebe.png

Respons dari layanan web diformat dalam salah satu format data umum, seperti XML (eXtensible Markup Language) atau JSON (JavaScript Object Notation). Format JSON mewakili data terstruktur dalam key-value pair. Aplikasi berkomunikasi dengan REST API menggunakan JSON, yang Anda pelajari lebih lanjut dalam tugas berikutnya.

Dalam tugas ini, Anda membuat koneksi jaringan ke server, berkomunikasi dengan server, dan menerima respons JSON. Anda akan menggunakan server backend yang sudah ditulis untuk Anda. Dalam codelab ini, Anda menggunakan library Retrofit, library pihak ketiga untuk berkomunikasi dengan server backend.

Library Eksternal

Library eksternal atau library pihak ketiga sama seperti ekstensi untuk API Android inti. Library yang Anda gunakan dalam kursus ini adalah open source, dikembangkan oleh komunitas, dan dikelola oleh kontribusi kolektif dari komunitas Android yang besar di seluruh dunia. Library ini membantu developer Android seperti Anda untuk membangun aplikasi yang lebih baik.

Library Retrofit

Library Retrofit yang Anda gunakan dalam codelab ini untuk berkomunikasi dengan layanan web RESTful Mars adalah contoh bagus dari library yang didukung dan dikelola dengan baik. Anda dapat mengetahuinya dengan melihat halaman GitHub dan meninjau masalah terbuka dan tertutup (beberapa di antaranya adalah permintaan fitur). Jika developer rutin mengatasi masalah dan merespons permintaan fitur, berarti library mungkin dikelola dengan baik dan merupakan kandidat yang baik untuk digunakan dalam aplikasi. Anda juga dapat melihat dokumentasi Retrofit untuk mempelajari library lebih lanjut.

Library Retrofit berkomunikasi dengan backend REST. Kode ini akan menghasilkan kode, tetapi Anda harus menyediakan URI untuk layanan web berdasarkan parameter yang kita teruskan. Anda akan mempelajari lebih lanjut topik ini di bagian berikutnya.

26043df178401c6a.png

Menambahkan dependensi Retrofit

Android Gradle memungkinkan Anda menambahkan library eksternal ke project Anda. Selain dependensi library, Anda juga perlu menyertakan repositori tempat library dihosting.

  1. Buka file gradle level modul build.gradle.kts (Module :app).
  2. Di bagian dependencies, tambahkan baris berikut untuk library Retrofit:
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Retrofit with Scalar Converter
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")

Kedua library bekerja bersama. Dependensi pertama adalah untuk library Retrofit2, dan dependensi kedua adalah untuk pengonversi skalar Retrofit. Retrofit2 adalah versi library Retrofit yang diperbarui. Pengonversi skalar ini memungkinkan Retrofit menampilkan hasil JSON sebagai String. JSON adalah format untuk menyimpan dan memindahkan data antara klien dan server. Anda akan mempelajari JSON di bagian selanjutnya.

  1. Klik Sync Now untuk membangun ulang project dengan dependensi baru.

6. Menghubungkan ke Internet

Anda menggunakan library Retrofit untuk berkomunikasi dengan layanan web Mars dan menampilkan respons JSON mentah sebagai String. Placeholder Text menampilkan string respons JSON yang ditampilkan atau pesan yang menunjukkan error koneksi.

Retrofit membuat API jaringan untuk aplikasi berdasarkan konten dari layanan web. Library ini mengambil data dari layanan web dan merutekannya melalui library pengonversi terpisah yang mengetahui cara mendekode data dan menampilkannya dalam bentuk objek, seperti String. Retrofit mencakup dukungan bawaan untuk format data populer, seperti XML dan JSON. Retrofit pada akhirnya membuat kode untuk memanggil dan memakai layanan ini untuk Anda, termasuk detail penting, seperti menjalankan permintaan pada thread latar belakang.

a78435c405c1e347.png

Dalam tugas ini, Anda menambahkan lapisan data ke project Mars Photos yang digunakan ViewModel untuk berkomunikasi dengan layanan web. Anda menerapkan API layanan Retrofit, dengan langkah-langkah berikut:

  • Buat sumber data, class MarsApiService.
  • Buat objek Retrofit dengan URL dasar dan factory pengonversi untuk mengonversi string.
  • Buat antarmuka yang menjelaskan cara Retrofit berkomunikasi dengan server web.
  • Buat layanan Retrofit dan ekspos instance-nya ke layanan API ke seluruh aplikasi.

Terapkan langkah-langkah di atas:

  1. Klik kanan pada paket com.example.marsphotos di panel project Android Anda dan pilih New > Package.
  2. Di pop-up, tambahkan network ke akhir nama paket yang disarankan.
  3. Buat file Kotlin baru di bawah paket baru. Beri nama MarsApiService.
  4. Buka network/MarsApiService.kt.
  5. Tambahkan konstanta berikut ke URL dasar untuk layanan web.
private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. Tambahkan builder Retrofit tepat di bawah konstanta tersebut untuk membangun dan membuat objek Retrofit.
import retrofit2.Retrofit

private val retrofit = Retrofit.Builder()

Retrofit memerlukan URI dasar untuk layanan web dan factory pengonversi untuk membangun API layanan web. Pengonversi memberi tahu Retrofit apa yang harus dilakukan dengan data yang didapat kembali dari layanan web. Dalam hal ini, Anda ingin Retrofit mengambil respons JSON dari layanan web dan menampilkannya sebagai String. Retrofit memiliki ScalarsConverter yang mendukung string dan jenis sederhana lainnya.

  1. Panggil addConverterFactory() pada builder dengan instance ScalarsConverterFactory.
import retrofit2.converter.scalars.ScalarsConverterFactory

private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
  1. Tambahkan URL dasar untuk layanan web menggunakan metode baseUrl().
  2. Panggil build() untuk membuat objek Retrofit.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()
  1. Pada panggilan ke builder Retrofit, tentukan antarmuka yang disebut MarsApiService yang menentukan cara Retrofit berkomunikasi dengan server web menggunakan permintaan HTTP.
interface MarsApiService {
}
  1. Tambahkan fungsi yang bernama getPhotos() ke antarmuka MarsApiService untuk mendapatkan string respons dari layanan web.
interface MarsApiService {
    fun getPhotos()
}
  1. Gunakan anotasi @GET untuk memberi tahu Retrofit bahwa ini adalah permintaan GET dan tentukan endpoint untuk metode layanan web tersebut. Dalam kasus ini, endpoint-nya adalah photos. Seperti yang disebutkan dalam tugas sebelumnya, Anda akan menggunakan endpoint /photos dalam codelab ini.
import retrofit2.http.GET

interface MarsApiService {
    @GET("photos")
    fun getPhotos()
}

Saat metode getPhotos() dipanggil, Retrofit menambahkan endpoint photos ke URL dasar—yang Anda tentukan dalam builder Retrofit—yang digunakan untuk memulai permintaan.

  1. Tambahkan jenis nilai yang ditampilkan fungsi ke String.
interface MarsApiService {
    @GET("photos")
    fun getPhotos(): String
}

Deklarasi objek

Pada Kotlin, deklarasi objek digunakan untuk mendeklarasikan objek singleton. Pola Singleton memastikan bahwa satu, dan hanya satu, instance objek yang dibuat dan memiliki satu titik akses global ke objek tersebut. Inisialisasi objek adalah thread-safe dan dilakukan pada akses pertama.

Berikut adalah contoh deklarasi objek dan aksesnya. Deklarasi objek selalu memiliki nama yang mengikuti kata kunci object.

Contoh:

// Example for Object declaration, do not copy over

object SampleDataProvider {
    fun register(provider: SampleProvider) {
        // ...
    }
​
    // ...
}

// To refer to the object, use its name directly.
SampleDataProvider.register(...)

Panggilan ke fungsi create() pada objek Retrofit sangat mahal dalam hal memori, kecepatan, dan performa. Aplikasi ini hanya memerlukan satu instance layanan Retrofit API, sehingga Anda mengekspos layanan ke seluruh aplikasi menggunakan deklarasi objek.

  1. Di luar deklarasi antarmuka MarsApiService, tentukan objek publik yang disebut MarsApi untuk menginisialisasi layanan Retrofit. Objek ini adalah objek singleton publik yang dapat diakses oleh aplikasi lainnya.
object MarsApi {}
  1. Di dalam deklarasi objek MarsApi, tambahkan properti objek retrofit yang diinisialisasi dengan lambat bernama retrofitService dari jenis MarsApiService. Anda melakukan inisialisasi lambat ini, untuk memastikan diinisialisasi saat penggunaan pertama. Abaikan error, yang Anda perbaiki pada langkah berikutnya.
object MarsApi {
    val retrofitService : MarsApiService by lazy {}
}
  1. Inisialisasi variabel retrofitService menggunakan metode retrofit.create() dengan antarmuka MarsApiService.
object MarsApi {
    val retrofitService : MarsApiService by lazy {
       retrofit.create(MarsApiService::class.java)
    }
}

Penyiapan Retrofit selesai! Setiap kali aplikasi Anda memanggil MarsApi.retrofitService, pemanggil akan mengakses objek Retrofit singleton yang sama yang menerapkan MarsApiService, yang dibuat pada akses pertama. Pada tugas berikutnya, Anda akan menggunakan objek Retrofit yang Anda implementasikan.

Memanggil layanan web di MarsViewModel

Pada langkah ini, Anda akan menerapkan metode getMarsPhotos() yang memanggil layanan REST, lalu menangani string JSON yang ditampilkan.

ViewModelScope

viewModelScope adalah cakupan coroutine bawaan yang ditentukan untuk setiap ViewModel dalam aplikasi Anda. Setiap coroutine yang diluncurkan dalam cakupan ini akan dibatalkan secara otomatis jika ViewModel dihapus.

Anda dapat menggunakan viewModelScope untuk meluncurkan coroutine dan membuat permintaan layanan web di latar belakang. Karena viewModelScope adalah milik ViewModel, permintaan akan berlanjut meskipun aplikasi mengalami perubahan konfigurasi.

  1. Di file MarsApiService.kt, jadikan getPhotos() sebagai fungsi penangguhan untuk menjadikannya asinkron dan tidak memblokir thread panggilan. Anda memanggil fungsi ini dari dalam viewModelScope.
@GET("photos")
suspend fun getPhotos(): String
  1. Buka file ui/screens/MarsViewModel.kt. Scroll ke bawah, ke metode getMarsPhotos(). Hapus baris yang menetapkan respons status ke "Set the Mars API Response here!" sehingga metode getMarsPhotos() kosong.
private fun getMarsPhotos() {}
  1. Di dalam getMarsPhotos(), luncurkan coroutine menggunakan viewModelScope.launch.
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

private fun getMarsPhotos() {
    viewModelScope.launch {}
}
  1. Di dalam viewModelScope, gunakan objek singleton MarsApi untuk memanggil metode getPhotos() dari antarmuka retrofitService. Simpan respons yang ditampilkan dalam val yang bernama listResult.
import com.example.marsphotos.network.MarsApi

viewModelScope.launch {
    val listResult = MarsApi.retrofitService.getPhotos()
}
  1. Tetapkan hasil yang baru saja diterima dari server backend ke marsUiState. marsUiState adalah objek status yang dapat diubah yang mewakili status permintaan web terbaru.
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = listResult
  1. Jalankan aplikasi. Perhatikan bahwa aplikasi segera ditutup, dan mungkin menampilkan atau tidak menampilkan pop-up error. Ini adalah error aplikasi.
  2. Klik tab Logcat di Android Studio dan catat error dalam log, yang diawali dengan baris seperti ini: "------- beginning of crash"
    --------- beginning of crash
22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.example.android.marsphotos, PID: 22803
    java.lang.SecurityException: Permission denied (missing INTERNET permission?)
...

Pesan error ini menunjukkan bahwa aplikasi mungkin tidak memiliki izin INTERNET. Tugas berikutnya menjelaskan cara menambahkan izin internet ke aplikasi dan menyelesaikan masalah ini.

7. Menambahkan izin Internet dan Penanganan Pengecualian

Izin Android

Tujuan izin di Android adalah untuk melindungi privasi pengguna Android. Aplikasi Android harus mendeklarasikan atau meminta izin untuk mengakses data pengguna yang sifatnya sensitif, seperti kontak, log panggilan, dan fitur sistem tertentu, seperti kamera atau internet.

Agar aplikasi Anda dapat mengakses Internet, aplikasi memerlukan izin INTERNET. Menghubungkan ke internet menimbulkan masalah keamanan, itulah sebabnya aplikasi tidak memiliki koneksi internet secara default. Anda harus secara eksplisit mendeklarasikan bahwa aplikasi memerlukan akses ke internet. Deklarasi ini dianggap sebagai izin normal. Untuk mempelajari izin Android dan jenisnya lebih lanjut, lihat Izin di Android.

Pada langkah ini, aplikasi Anda mendeklarasikan izin yang diperlukan dengan menyertakan tag <uses-permission> dalam file AndroidManifest.xml.

  1. Buka manifests/AndroidManifest.xml. Tambahkan baris ini sebelum tag <application>:
<uses-permission android:name="android.permission.INTERNET" />
  1. Kompilasi dan jalankan aplikasi lagi.

Jika memiliki koneksi internet yang aktif, Anda akan melihat teks JSON yang berisi data terkait foto Mars. Perhatikan bagaimana id dan img_src diulang untuk setiap catatan gambar. Anda akan mempelajari lebih lanjut format JSON nanti di codelab.

b82ddb79eff61995.png

  1. Ketuk tombol Kembali di perangkat atau emulator untuk menutup aplikasi.

Penanganan Pengecualian

Ada bug dalam kode Anda. Lakukan langkah-langkah berikut untuk melihatnya:

  1. Setel perangkat atau emulator ke Mode Pesawat untuk menyimulasikan error koneksi jaringan.
  2. Buka kembali aplikasi dari menu Terbaru, atau jalankan aplikasi dari Android Studio.
  3. Klik tab Logcat di Android Studio dan catat pengecualian fatal dalam log, yang terlihat seperti berikut ini:
3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.android.marsphotos, PID: 3302

Pesan error ini menunjukkan aplikasi mencoba untuk terhubung dan waktu habis. Pengecualian seperti ini sangat umum terjadi secara real time. Tidak seperti masalah izin, error ini bukan masalah yang dapat Anda perbaiki, tetapi Anda dapat menanganinya. Pada langkah berikutnya, Anda mempelajari cara menangani pengecualian tersebut.

Pengecualian

Pengecualian adalah error yang dapat terjadi selama runtime, bukan waktu kompilasi, dan menghentikan aplikasi secara tiba-tiba tanpa memberi tahu pengguna. Hal ini dapat mengakibatkan pengalaman pengguna yang buruk. Penanganan pengecualian adalah mekanisme yang digunakan untuk mencegah aplikasi berhenti tiba-tiba dan menangani situasi dengan cara yang mudah digunakan.

Alasan pengecualian bisa sesederhana pembagian dengan nol atau error dengan koneksi jaringan. Pengecualian ini mirip dengan IllegalArgumentException yang dibahas di codelab sebelumnya.

Contoh potensi masalah saat menghubungkan ke server meliputi:

  • URL atau URI yang digunakan di API salah.
  • Server tidak tersedia, dan aplikasi tidak dapat terhubung.
  • Masalah latensi jaringan.
  • Koneksi internet buruk atau tidak ada di perangkat.

Pengecualian ini tidak dapat ditangani selama waktu kompilasi, tetapi Anda dapat menggunakan blok try-catch untuk menangani pengecualian dalam runtime. Untuk mempelajari lebih lanjut, baca Pengecualian.

Contoh sintaksis untuk blok try-catch

try {
    // some code that can cause an exception.
}
catch (e: SomeException) {
    // handle the exception to avoid abrupt termination.
}

Di dalam blok try, Anda akan menambahkan kode tempat mengantisipasi pengecualian. Di aplikasi Anda, ini adalah panggilan jaringan. Di blok catch, Anda harus menerapkan kode yang mencegah penghentian aplikasi secara tiba-tiba. Jika ada pengecualian, blok catch akan dijalankan untuk pulih dari error, bukan menghentikan aplikasi secara tiba-tiba.

  1. Di getMarsPhotos(), di dalam blok launch, tambahkan blok try di sekitar panggilan MarsApi untuk menangani pengecualian.
  2. Tambahkan blok catch setelah blok try.
import java.io.IOException

viewModelScope.launch {
   try {
       val listResult = MarsApi.retrofitService.getPhotos()
       marsUiState = listResult
   } catch (e: IOException) {

   }
}
  1. Jalankan aplikasi sekali lagi. Perhatikan bahwa aplikasi tidak mengalami error kali ini.

Menambahkan UI Status

Di class MarsViewModel, status permintaan web terbaru, marsUiState, disimpan sebagai objek status yang dapat diubah. Namun, class ini tidak memiliki kemampuan untuk menyimpan status yang berbeda: memuat, berhasil, dan gagal.

  • Status Memuat menunjukkan bahwa aplikasi sedang menunggu data.
  • Status Berhasil menunjukkan bahwa data berhasil diambil dari layanan web.
  • Status Error menunjukkan error jaringan atau koneksi.

Untuk mewakili ketiga status ini dalam aplikasi, Anda menggunakan antarmuka tertutup. sealed interface memudahkan pengelolaan status dengan membatasi kemungkinan nilai. Di aplikasi Mars Photos, Anda membatasi respons web marsUiState ke tiga status (objek class data): memuat, berhasil, dan error, yang terlihat seperti kode berikut:

// No need to copy over
sealed interface MarsUiState {
   data class Success : MarsUiState
   data class Loading : MarsUiState
   data class Error : MarsUiState
}

Dalam cuplikan kode di atas, untuk respons yang berhasil, Anda akan menerima informasi foto Mars dari server. Untuk menyimpan data, tambahkan parameter konstruktor ke class data Success.

Dalam kasus status Loading dan Error, Anda tidak perlu menetapkan data baru dan membuat objek baru; Anda hanya meneruskan respons web. Ubah class data menjadi Object guna membuat objek untuk respons web.

  1. Buka file ui/MarsViewModel.kt. Setelah pernyataan impor, tambahkan antarmuka tertutup MarsUiState. Penambahan ini membuat nilai objek MarsUiState dapat dilengkapi.
sealed interface MarsUiState {
    data class Success(val photos: String) : MarsUiState
    object Error : MarsUiState
    object Loading : MarsUiState
}
  1. Di dalam class MarsViewModel, perbarui definisi marsUiState. Ubah jenis ke MarsUiState dan MarsUiState.Loading sebagai nilai defaultnya. Buat penyetel menjadi pribadi untuk melindungi penulisan ke marsUiState.
var marsUiState: MarsUiState by mutableStateOf(MarsUiState.Loading)
  private set
  1. Scroll ke bawah, ke metode getMarsPhotos(). Perbarui nilai marsUiState ke MarsUiState.Success dan teruskan listResult.
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = MarsUiState.Success(listResult)
  1. Di dalam blok catch, tangani respons kegagalan. Setel MarsUiState ke Error.
catch (e: IOException) {
   marsUiState = MarsUiState.Error
}
  1. Anda dapat mencabut penetapan marsUiState dari blok try-catch. Fungsi Anda yang sudah selesai akan terlihat seperti kode berikut:
private fun getMarsPhotos() {
   viewModelScope.launch {
       marsUiState = try {
           val listResult = MarsApi.retrofitService.getPhotos()
           MarsUiState.Success(listResult)
       } catch (e: IOException) {
           MarsUiState.Error
       }
   }
}
  1. Dalam file screens/HomeScreen.kt, tambahkan ekspresi when di marsUiState. Jika marsUiState adalah MarsUiState.Success, panggil ResultScreen dan teruskan marsUiState.photos. Abaikan error untuk saat ini.
import androidx.compose.foundation.layout.fillMaxWidth

fun HomeScreen(
   marsUiState: MarsUiState,
   modifier: Modifier = Modifier
) {
    when (marsUiState) {
        is MarsUiState.Success -> ResultScreen(
            marsUiState.photos, modifier = modifier.fillMaxWidth()
        )
    }
}
  1. Di dalam blok when, tambahkan pemeriksaan untuk MarsUiState.Loading dan MarsUiState.Error. Minta aplikasi menampilkan composable LoadingScreen, ResultScreen, dan ErrorScreen yang Anda terapkan nanti.
import androidx.compose.foundation.layout.fillMaxSize

fun HomeScreen(
   marsUiState: MarsUiState,
   modifier: Modifier = Modifier
) {
    when (marsUiState) {
        is MarsUiState.Loading -> LoadingScreen(modifier = modifier.fillMaxSize())
        is MarsUiState.Success -> ResultScreen(
            marsUiState.photos, modifier = modifier.fillMaxWidth()
        )

        is MarsUiState.Error -> ErrorScreen( modifier = modifier.fillMaxSize())
    }
}
  1. Buka res/drawable/loading_animation.xml. Drawable ini adalah animasi yang memutar drawable gambar, loading_img.xml, mengitari titik tengah. (Anda tidak melihat animasinya di pratinjau.)

92a448fa23b6d1df.png

  1. Pada file screens/HomeScreen.kt, di bawah composable HomeScreen, tambahkan fungsi composable LoadingScreen berikut untuk menampilkan animasi pemuatan. Resource drawable loading_img disertakan dalam kode awal.
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.Image

@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
    Image(
        modifier = modifier.size(200.dp),
        painter = painterResource(R.drawable.loading_img),
        contentDescription = stringResource(R.string.loading)
    )
}
  1. Di bawah composable LoadingScreen, tambahkan fungsi composable ErrorScreen berikut sehingga aplikasi dapat menampilkan pesan error.
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding

@Composable
fun ErrorScreen(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_connection_error), contentDescription = ""
        )
        Text(text = stringResource(R.string.loading_failed), modifier = Modifier.padding(16.dp))
    }
}
  1. Jalankan aplikasi lagi dengan mengaktifkan Mode Pesawat. Aplikasi tidak menutup sendiri sekarang, dan menampilkan pesan error berikut:

28ba37928e0a9334.png

  1. Nonaktifkan Mode Pesawat di ponsel atau emulator. Jalankan dan uji aplikasi Anda untuk memastikan semuanya berfungsi dengan benar dan Anda dapat melihat string JSON.

8. Mengurai respons JSON dengan kotlinx.serialization

JSON

Data yang diminta biasanya diformat dalam salah satu format data umum seperti XML atau JSON. Setiap panggilan menampilkan data terstruktur, dan aplikasi Anda perlu mengetahui struktur tersebut untuk membaca data dari respons.

Misalnya, di aplikasi ini, Anda mengambil data dari server https:// android-kotlin-fun-mars-server.appspot.com/photos. Saat memasukkan URL ini di browser, Anda akan melihat daftar ID dan URL gambar permukaan Mars dalam format JSON.

Struktur respons JSON sampel

menampilkan nilai kunci dan objek JSON

Struktur respons JSON memiliki fitur berikut:

  • Respons JSON adalah array, yang ditunjukkan dengan tanda kurung siku. Array berisi objek JSON.
  • Objek JSON dikelilingi oleh tanda kurung kurawal.
  • Setiap objek JSON berisi kumpulan key-value pair yang dipisahkan oleh koma.
  • Titik dua memisahkan kunci dan nilai dalam pasangan.
  • Nama dikelilingi oleh tanda kutip.
  • Nilai dapat berupa angka, string, boolean, array, objek (objek JSON), atau null.

Misalnya, img_src adalah URL, yang berupa string. Saat menempelkan URL ke browser web, Anda akan melihat gambar permukaan Mars.

b4f9f196c64f02c3.png

Di aplikasi, sekarang Anda mendapatkan respons JSON dari layanan web Mars, yang merupakan awal yang baik. Namun, yang benar-benar Anda perlukan untuk menampilkan gambar adalah objek Kotlin, bukan string JSON besar. Proses ini disebut deserialisasi.

Serialisasi adalah proses mengonversi data yang digunakan oleh aplikasi ke format yang dapat ditransfer melalui jaringan. Berbeda dengan serialisasi, deserialisasi adalah proses membaca data dari sumber eksternal (seperti server) dan mengonversinya menjadi objek runtime. Keduanya adalah komponen penting dari sebagian besar aplikasi yang bertukar data melalui jaringan.

kotlinx.serialization menyediakan kumpulan library yang mengonversi string JSON menjadi objek Kotlin. Ada komunitas yang dikembangkan oleh library pihak ketiga yang berfungsi dengan Retrofit, Pengonversi Serialisasi Kotlin.

Dalam tugas ini, Anda menggunakan library kotlinx.serialization, untuk mengurai respons JSON dari layanan web menjadi objek Kotlin berguna yang menampilkan foto Mars. Anda mengubah aplikasi sehingga tidak menampilkan JSON mentah, aplikasi akan menampilkan jumlah foto Mars yang dikembalikan.

Menambahkan dependensi library kotlinx.serialization

  1. Buka build.gradle.kts (Module :app).
  2. Di blok plugins, tambahkan plugin kotlinx serialization.
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10"
  1. Di bagian dependencies, tambahkan kode berikut untuk menyertakan dependensi kotlinx.serialization. Dependensi ini menyediakan serialisasi JSON untuk project Kotlin.
// Kotlin serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
  1. Temukan baris untuk pengonversi skalar Retrofit di blok dependencies lalu ubah untuk menggunakan kotlinx-serialization-converter:

Ganti kode berikut

// Retrofit with scalar Converter
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")

dengan kode berikut

// Retrofit with Kotlin serialization Converter

implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
  1. Klik Sync Now untuk membangun ulang project dengan dependensi baru.

Menerapkan class data Mars Photo

Contoh entri respons JSON yang Anda dapatkan dari layanan web terlihat seperti berikut, mirip dengan yang Anda lihat sebelumnya:

[
    {
        "id":"424906",
        "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
    },
...]

Pada contoh di atas, perhatikan bahwa setiap entri foto Mars memiliki pasangan kunci dan nilai JSON berikut:

  • id: ID properti, sebagai string. Karena diawali dan diakhiri dengan tanda kutip (" "), jenis file tersebut adalah String, bukan Integer.
  • img_src: URL gambar, sebagai string.

kotlinx.serialization mengurai data JSON ini dan mengubahnya menjadi objek Kotlin. Untuk melakukannya, kotlinx.serialization perlu memiliki class data Kotlin untuk menyimpan hasil yang diuraikan. Pada langkah ini, Anda akan membuat class data MarsPhoto.

  1. Klik kanan pada paket network dan pilih New > Kotlin File/Class.
  2. Di dialog pilih Class dan masukkan MarsPhoto sebagai nama class. Tindakan ini akan membuat file baru bernama MarsPhoto.kt dalam paket network.
  3. Jadikan MarsPhoto class data dengan menambahkan kata kunci data sebelum definisi class.
  4. Ubah tanda kurung kurawal {} menjadi tanda kurung (). Perubahan ini akan menimbulkan error karena class data harus memiliki setidaknya satu properti yang ditentukan.
data class MarsPhoto()
  1. Tambahkan properti berikut ke definisi class MarsPhoto.
data class MarsPhoto(
    val id: String,  val img_src: String
)
  1. Untuk membuat class MarsPhoto yang dapat diserialisasi dianotasi dengan @Serializable.
import kotlinx.serialization.Serializable

@Serializable
data class MarsPhoto(
    val id: String,  val img_src: String
)

Perhatikan bahwa setiap variabel di class MarsPhoto sesuai dengan nama kunci dalam objek JSON. Untuk mencocokkan jenis dalam respons JSON tertentu, Anda menggunakan objek String untuk semua nilai.

Saat kotlinx serialization mengurai JSON, fungsi tersebut akan mencocokkan kunci menurut nama dan mengisi objek data dengan nilai yang sesuai.

Anotasi @SerialName

Terkadang nama kunci dalam respons JSON dapat membuat properti Kotlin membingungkan atau mungkin tidak cocok dengan gaya coding yang disarankan. Misalnya, dalam file JSON, kunci img_src menggunakan garis bawah, sedangkan konvensi Kotlin untuk properti menggunakan huruf besar dan huruf kecil (camel case).

Untuk menggunakan nama variabel di class data yang berbeda dari nama kunci dalam respons JSON, gunakan anotasi @SerialName. Pada contoh berikut, nama variabel di class data adalah imgSrc. Variabel dapat dipetakan ke atribut JSON img_src menggunakan @SerialName(value = "img_src").

  1. Ganti baris untuk kunci img_src dengan baris yang ditampilkan di bawah.
import kotlinx.serialization.SerialName

@SerialName(value = "img_src")
val imgSrc: String

Mengupdate MarsApiService dan MarsViewModel

Dalam tugas ini, Anda akan menggunakan pengonversi kotlinx.serialization untuk mengonversi objek JSON ke objek Kotlin.

  1. Buka network/MarsApiService.kt.
  2. Perhatikan error referensi yang belum terselesaikan untuk ScalarsConverterFactory. Error ini adalah hasil dari perubahan dependensi Retrofit di bagian sebelumnya.
  3. Hapus impor untuk ScalarConverterFactory. Anda memperbaiki error lainnya nanti.

Hapus:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. Pada deklarasi objek retrofit, ubah builder Retrofit untuk menggunakan kotlinx.serialization, bukan ScalarConverterFactory.
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType

private val retrofit = Retrofit.Builder()
        .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
        .baseUrl(BASE_URL)
        .build()

Setelah menempatkan kotlinx.serialization, Anda dapat meminta Retrofit untuk menampilkan daftar objek MarsPhoto dari array JSON, bukan menampilkan string JSON.

  1. Perbarui antarmuka MarsApiService untuk Retrofit guna menampilkan daftar objek MarsPhoto, bukan String.
interface MarsApiService {
    @GET("photos")
    suspend fun getPhotos(): List<MarsPhoto>
}
  1. Buat perubahan yang serupa pada viewModel. Buka MarsViewModel.kt dan scroll ke bawah ke metode getMarsPhotos().

Dalam metode getMarsPhotos(), listResult adalah List<MarsPhoto>, bukan String lagi. Ukuran daftar tersebut adalah jumlah foto yang diterima dan diuraikan.

  1. Untuk mencetak jumlah foto yang diambil, update marsUiState sebagai berikut:
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = MarsUiState.Success(
   "Success: ${listResult.size} Mars photos retrieved"
)
  1. Pastikan Mode Pesawat dinonaktifkan pada perangkat atau emulator. Kompilasi dan jalankan aplikasi.

Kali ini, pesan akan menampilkan jumlah properti yang ditampilkan dari layanan web, dan bukan string JSON besar:

a59e55909b6e9213.png

9. Kode solusi

Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git
$ cd basic-android-kotlin-compose-training-mars-photos
$ git checkout repo-starter

Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi untuk codelab ini, lihat kode tersebut di GitHub.

10. Ringkasan

Layanan web REST

  • Layanan web adalah fungsi berbasis software, yang ditawarkan melalui internet, yang memungkinkan aplikasi Anda membuat permintaan dan mendapatkan data kembali.
  • Layanan web umum menggunakan arsitektur REST. Layanan web yang menawarkan arsitektur REST disebut sebagai layanan RESTful. Layanan web RESTful dibuat menggunakan komponen dan protokol web standar.
  • Anda membuat permintaan ke layanan web REST dengan cara standar melalui URI.
  • Untuk menggunakan layanan web, aplikasi harus membuat koneksi jaringan dan berkomunikasi dengan layanan. Kemudian, aplikasi harus menerima dan mengurai data respons ke dalam format yang dapat digunakan aplikasi.
  • Library Retrofit adalah library klien yang memungkinkan aplikasi Anda membuat permintaan ke layanan web REST.
  • Gunakan pengonversi untuk memberi tahu Retrofit apa yang harus dilakukan dengan data yang dikirimnya ke layanan web dan didapatkan kembali dari layanan web. Misalnya, ScalarsConverter memperlakukan data layanan web sebagai String atau primitif lainnya.
  • Agar aplikasi Anda dapat terhubung ke internet, tambahkan izin "android.permission.INTERNET" dalam manifes Android.
  • Inisialisasi lambat mendelegasikan pembuatan objek saat pertama kali digunakan. Tindakan ini akan membuat referensi, tetapi tidak membuat objek. Saat objek diakses untuk pertama kalinya, referensi akan dibuat dan digunakan setiap kali setelahnya.

Penguraian JSON

  • Respons dari layanan web sering kali diformat dalam JSON, format umum untuk mewakili data terstruktur.
  • Objek JSON adalah kumpulan pasangan nilai-kunci.
  • Koleksi objek JSON adalah array JSON. Anda mendapatkan array JSON sebagai respons dari layanan web.
  • Kunci dalam pasangan nilai kunci dikelilingi oleh tanda kutip. Nilai dapat berupa angka atau string.
  • Di Kotlin, alat serialisasi data tersedia dalam komponen terpisah, kotlinx.serialization. kotlinx.serialization menyediakan kumpulan library yang mengonversi string JSON menjadi objek Kotlin.
  • Ada library Kotlin Pengonversi Serialisasi yang dikembangkan komunitas untuk Retrofit: retrofit2-kotlinx-serialization-converter.
  • kotlinx.serialization mencocokkan kunci dalam respons JSON dengan properti dalam objek data yang memiliki nama yang sama.
  • Agar dapat menggunakan nama properti yang berbeda untuk sebuah kunci, anotasikan properti tersebut dengan anotasi @SerialName dan value kunci JSON.

11. Pelajari lebih lanjut

Dokumentasi developer Android:

Dokumentasi Kotlin:

Lainnya: