Dasar-Dasar Android Kotlin 06.2: Coroutine dan Room

Codelab ini adalah bagian dari kursus Dasar-Dasar Kotlin Android. Anda akan mendapatkan manfaat maksimal dari kursus ini jika Anda mengerjakan codelab secara berurutan. Semua codelab kursus tercantum di halaman landing codelab Dasar-Dasar Android Kotlin.

Pengantar

Salah satu prioritas utama dalam menciptakan pengalaman pengguna yang sempurna untuk aplikasi Anda adalah memastikan UI selalu responsif dan berjalan dengan lancar. Salah satu cara untuk meningkatkan performa UI adalah dengan memindahkan tugas yang berjalan lama, seperti operasi database, ke latar belakang.

Dalam codelab ini, Anda akan menerapkan bagian yang menghadap pengguna ke aplikasi TrackMySleepQuality, menggunakan coroutine Kotlin untuk melakukan operasi database jauh dari thread utama.

Yang harus sudah Anda ketahui

Anda harus memahami:

  • Membuat antarmuka pengguna (UI) dasar menggunakan aktivitas, fragmen, tampilan, dan pengendali klik.
  • Menavigasi antar-fragmen, dan menggunakan safeArgs untuk meneruskan data sederhana antar-fragmen.
  • Melihat model, menampilkan pabrik model, transformasi, dan LiveData.
  • Cara membuat database Room, membuat DAO, dan menentukan entity.
  • Hal ini berguna jika Anda terbiasa dengan konsep threading dan multipemrosesan.

Yang akan Anda pelajari

  • Cara kerja thread di Android.
  • Cara menggunakan coroutine Kotlin untuk menjauhkan operasi database dari thread utama.
  • Cara menampilkan data berformat di TextView.

Yang akan Anda lakukan

  • Perluas aplikasi TrackMySleepQuality untuk mengumpulkan, menyimpan, dan menampilkan data di dalam dan dari database.
  • Gunakan coroutine untuk menjalankan operasi database yang berjalan lama di latar belakang.
  • Gunakan LiveData untuk memicu navigasi dan tampilan snackbar.
  • Gunakan LiveData untuk mengaktifkan dan menonaktifkan tombol.

Dalam codelab ini, Anda akan mem-build bagian model tampilan, coroutine, dan tampilan data dari aplikasi TrackMySleepQuality.

Aplikasi memiliki dua layar, yang direpresentasikan oleh fragmen, seperti yang ditunjukkan pada gambar di bawah ini.

Layar pertama, yang ditampilkan di sebelah kiri, memiliki tombol untuk memulai dan menghentikan pelacakan. Layar menampilkan semua data tidur pengguna. Tombol Hapus secara permanen menghapus semua data yang telah dikumpulkan oleh aplikasi untuk pengguna.

Layar kedua, yang ditampilkan di sebelah kanan, adalah untuk memilih rating kualitas tidur. Di aplikasi, rating direpresentasikan secara numerik. Untuk tujuan pengembangan, aplikasi menampilkan ikon wajah dan ikon numeriknya.

Alur penggunaannya adalah sebagai berikut:

  • Pengguna akan membuka aplikasi dan melihat layar pelacakan tidur.
  • Pengguna mengetuk tombol Start. Ini akan merekam waktu mulai dan menampilkannya. Tombol Start dinonaktifkan, dan tombol Stop diaktifkan.
  • Pengguna mengetuk tombol Stop. Tindakan ini akan mencatat waktu berakhir dan membuka layar kualitas tidur.
  • Pengguna memilih ikon kualitas tidur. Layar akan ditutup, dan layar pelacakan menampilkan waktu tidur dan kualitas tidur. Tombol Stop dinonaktifkan dan tombol Start diaktifkan. Aplikasi siap untuk malam lainnya.
  • Tombol Hapus diaktifkan setiap kali ada data dalam database. Saat pengguna mengetuk tombol Hapus, semua data mereka akan dihapus tanpa bantuan—tidak ada pesan "Apakah Anda yakin?".

Aplikasi ini menggunakan arsitektur yang disederhanakan, seperti yang ditampilkan di bawah ini dalam konteks arsitektur lengkap. Aplikasi hanya menggunakan komponen berikut:

  • Pengontrol UI
  • Lihat model dan LiveData
  • Database Room

Dalam tugas ini, Anda menggunakan TextView untuk menampilkan data pelacakan tidur berformat. (Ini bukan antarmuka akhir. Anda akan mempelajari cara yang lebih baik di codelab lain.)

Anda dapat melanjutkan dengan aplikasi TrackMySleepQuality yang Anda build di codelab sebelumnya atau mendownload aplikasi awal untuk codelab ini.

Langkah 1: Download dan jalankan aplikasi awal

  1. Download aplikasi TrackMySleepQuality-Coroutines-Starter dari GitHub.
  2. Mem-build dan menjalankan aplikasi. Aplikasi menampilkan UI untuk fragmen SleepTrackerFragment, tetapi tidak memiliki data. Tombol tersebut tidak merespons ketukan.

Langkah 2: Periksa kode

Kode awal untuk codelab ini sama dengan kode solusi untuk codelab database Room 6.1 Create.

  1. Buka res/layout/activity_main.xml. Tata letak ini berisi fragmen nav_host_fragment. Selain itu, perhatikan tag <merge>.

    Tag merge dapat digunakan untuk menghilangkan tata letak yang berlebihan saat menyertakan tata letak, dan sebaiknya gunakan. Contoh tata letak yang berlebihan adalah ConstraintLayout > LinearLayout > TextView, tempat sistem mungkin dapat menghilangkan LinearLayout. Pengoptimalan semacam ini dapat menyederhanakan hierarki tampilan dan meningkatkan performa aplikasi.
  2. Di folder navigation, buka navigation.xml. Anda dapat melihat dua fragmen dan tindakan navigasi yang menghubungkannya.
  3. Dalam folder tata letak, klik dua kali fragmen pelacak tidur untuk melihat tata letak XML-nya. Perhatikan hal berikut ini:
  • Data tata letak digabungkan dalam elemen <layout> untuk mengaktifkan data binding.
  • ConstraintLayout dan tampilan lainnya diatur di dalam elemen <layout>.
  • File memiliki tag <data> placeholder.

Aplikasi awal juga menyediakan dimensi, warna, dan gaya untuk UI. Aplikasi ini berisi database Room, DAO, dan entitas SleepNight. Jika Anda tidak menyelesaikan codelab sebelumnya, pastikan untuk menjelajahi aspek kode ini sendiri.

Setelah memiliki database dan UI, Anda harus mengumpulkan data, menambahkan data ke database, dan menampilkan data. Semua pekerjaan ini dilakukan dalam model tampilan. Model tampilan pelacak tidur Anda akan menangani klik tombol, berinteraksi dengan database melalui DAO, dan memberikan data ke UI melalui LiveData. Semua operasi database harus dijalankan dari UI thread utama dan Anda akan melakukannya menggunakan coroutine.

Langkah 1: Tambahkan SleepTrackerViewModel

  1. Dalam paket sleeptracker, buka SleepTrackerViewModel.kt.
  2. Periksa class SleepTrackerViewModel, yang disediakan untuk Anda di aplikasi awal dan juga ditampilkan di bawah. Perhatikan bahwa class tersebut memperluas AndroidViewModel(). Class ini sama dengan ViewModel, tetapi menggunakan konteks aplikasi sebagai parameter dan membuatnya tersedia sebagai properti. Anda akan memerlukannya nanti.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Langkah 2: Tambahkan SleepTrackerViewModelFactory

  1. Dalam paket sleeptracker, buka SleepTrackerViewModelFactory.kt.
  2. Periksa kode yang disediakan untuk Anda ke setelan pabrik, yang ditampilkan di bawah:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Perhatikan hal berikut ini:

  • SleepTrackerViewModelFactory yang diberikan menggunakan argumen yang sama seperti ViewModel dan memperluas ViewModelProvider.Factory.
  • Di dalam factory, kode mengganti create(), yang menggunakan jenis class apa pun sebagai argumen dan menampilkan ViewModel.
  • Dalam isi create(), kode akan memeriksa apakah ada class SleepTrackerViewModel yang tersedia, dan jika ada, akan menampilkan instance-nya. Jika tidak, kode akan melempar pengecualian.

Langkah 3: Perbarui SleepTrackerFragment

  1. Di SleepTrackerFragment, dapatkan referensi ke konteks aplikasi. Tempatkan referensi dalam onCreateView(), di bawah binding. Anda memerlukan referensi ke aplikasi tempat fragmen ini dilampirkan, untuk diteruskan ke penyedia factory model tampilan.

    Fungsi Kotlin requireNotNull menampilkan IllegalArgumentException jika nilai adalah null.
val application = requireNotNull(this.activity).application
  1. Anda memerlukan referensi ke sumber data Anda melalui referensi ke DAO. Di onCreateView(), sebelum return, tentukan dataSource. Untuk mendapatkan referensi ke DAO database, gunakan SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. Di onCreateView(), sebelum return, buat instance viewModelFactory. Anda harus meneruskan dataSource dan application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Setelah memiliki factory, dapatkan referensi ke SleepTrackerViewModel. Parameter SleepTrackerViewModel::class.java mengacu pada class Java runtime objek ini.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. Kode yang sudah selesai akan terlihat seperti ini:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

Berikut metode onCreateView() sejauh ini:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

Langkah 4: Tambahkan data binding untuk model tampilan

Dengan menerapkan ViewModel dasar, Anda harus menyelesaikan penyiapan data binding di SleepTrackerFragment untuk menghubungkan ViewModel dengan UI.


Di file tata letak fragment_sleep_tracker.xml:

  1. Di dalam blok <data>, buat <variable> yang mereferensikan class SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

Di SleepTrackerFragment:

  1. Menetapkan aktivitas saat ini sebagai pemilik siklus proses binding. Tambahkan kode ini di dalam metode onCreateView(), sebelum pernyataan return:
binding.setLifecycleOwner(this)
  1. Tetapkan variabel binding sleepTrackerViewModel ke sleepTrackerViewModel. Letakkan kode ini di dalam onCreateView(), di bawah kode yang membuat SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Anda mungkin akan melihat error, karena Anda harus membuat ulang objek binding. Bersihkan dan buat ulang proyek untuk menghilangkan kesalahan.
  2. Terakhir, seperti biasa, pastikan kode Anda build dan berjalan tanpa error.

Di Kotlin, coroutine adalah cara untuk menangani tugas yang berjalan lama dengan elegan dan efisien. Coroutine Kotlin memungkinkan Anda mengonversi kode berbasis callback menjadi kode berurutan. Kode yang ditulis secara berurutan biasanya lebih mudah dibaca dan bahkan dapat menggunakan fitur bahasa seperti pengecualian. Pada akhirnya, coroutine dan callback melakukan hal yang sama: coroutine menunggu hingga hasil tersedia dari tugas yang berjalan lama dan melanjutkan eksekusi.

Coroutine memiliki properti berikut:

  • Coroutine bersifat asinkron dan tidak memblokir.
  • Coroutine menggunakan fungsi penangguhan untuk membuat kode asinkron berurutan.

Coroutine bersifat asinkron.

Coroutine berjalan secara independen dari langkah-langkah eksekusi utama program Anda. Ini dapat dilakukan secara paralel atau pada prosesor terpisah. Mungkin juga saat seluruh aplikasi menunggu input, Anda akan mendapatkan sedikit pemrosesan. Salah satu aspek penting asinkron adalah Anda tidak dapat mengharapkan bahwa hasilnya tersedia, hingga Anda secara eksplisit menunggunya.

Misalnya, Anda memiliki pertanyaan yang memerlukan riset, dan Anda meminta rekan kerja untuk menemukan jawabannya. Mereka berbunyi dan mengerjakannya, yang seperti mereka melakukan pekerjaan "secara asinkron" dan "pada utas terpisah." Anda dapat terus melakukan pekerjaan lain yang tidak bergantung pada jawabannya, sampai rekan kerja Anda kembali dan memberi tahu apa jawabannya.

Coroutine tidak memblokir.

Non-pemblokiran berarti bahwa coroutine tidak memblokir thread utama atau UI. Jadi dengan coroutine, pengguna selalu memiliki pengalaman selancar mungkin, karena interaksi UI selalu memiliki prioritas.

Coroutine menggunakan fungsi penangguhan untuk membuat kode asinkron berurutan.

Kata kunci suspend adalah cara Kotlin untuk menandai fungsi atau jenis fungsi sebagai tersedia untuk coroutine. Jika coroutine memanggil fungsi yang ditandai dengan suspend, daripada memblokir hingga fungsi kembali seperti panggilan fungsi normal, coroutine akan menangguhkan eksekusi hingga hasilnya siap. Kemudian coroutine melanjutkan dari tempat terakhirnya, dengan hasilnya.

Saat coroutine ditangguhkan dan menunggu hasilnya, coroutine akan berhenti memblokir yang sedang berjalan tersebut. Dengan demikian, fungsi atau coroutine lain dapat berjalan.

Kata kunci suspend tidak menentukan thread tempat kode dijalankan. Fungsi penangguhan dapat berjalan pada thread latar belakang, atau pada thread utama.

Untuk menggunakan coroutine di Kotlin, Anda memerlukan tiga hal:

  • Pekerjaan
  • Petugas operator
  • Cakupan

Pekerjaan: Pada dasarnya, pekerjaan adalah sesuatu yang dapat dibatalkan. Setiap coroutine memiliki tugas, dan Anda dapat menggunakan tugas tersebut untuk membatalkan coroutine. Pekerjaan dapat disusun menjadi hierarki induk-turunan. Membatalkan tugas induk akan segera membatalkan semua turunan tugas, yang jauh lebih mudah daripada membatalkan setiap coroutine secara manual.

Petugas operator: Petugas operator mengirimkan coroutine untuk dijalankan pada berbagai untaian. Misalnya, Dispatcher.Main menjalankan tugas di thread utama, dan Dispatcher.IO mengalihkan tugas I/O yang memblokir ke kumpulan thread bersama.

Cakupan: Coroutine<#39;s cakupan menentukan konteks tempat coroutine berjalan. Cakupan menggabungkan informasi tentang pekerjaan dan dispatcher coroutine. Cakupan melacak coroutine. Saat Anda meluncurkan coroutine, ini akan menjadi "cakupan", yang berarti Anda telah menunjukkan cakupan yang akan melacak coroutine.

Anda ingin pengguna dapat berinteraksi dengan data tidur dengan cara berikut:

  • Saat pengguna mengetuk tombol Start, aplikasi akan membuat malam tidur baru dan menyimpan malam tidur dalam database.
  • Saat pengguna mengetuk tombol Stop, aplikasi akan memperbarui malam dengan waktu berakhir.
  • Saat pengguna mengetuk tombol Hapus, aplikasi akan menghapus data dalam database.

Operasi database ini dapat memerlukan waktu yang lama, sehingga harus berjalan di thread terpisah.

Langkah 1: Menyiapkan coroutine untuk operasi database

Saat tombol Start di aplikasi Sleep Tracker diketuk, Anda ingin memanggil fungsi di SleepTrackerViewModel untuk membuat instance SleepNight baru dan menyimpan instance di database.

Mengetuk salah satu tombol akan memicu operasi database, seperti membuat atau memperbarui SleepNight. Karena alasan ini dan lainnya, Anda menggunakan coroutine untuk menerapkan pengendali klik untuk tombol aplikasi.

  1. Buka file build.gradle tingkat aplikasi dan temukan dependensi untuk coroutine. Untuk menggunakan coroutine, Anda memerlukan dependensi ini yang telah ditambahkan untuk Anda.

    $coroutine_version ditentukan dalam file project build.gradle sebagai coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Buka file SleepTrackerViewModel.
  2. Dalam isi class, tentukan viewModelJob dan tetapkan instance Job. viewModelJob ini memungkinkan Anda membatalkan semua coroutine yang dimulai oleh model tampilan ini saat model tampilan tidak lagi digunakan dan dihancurkan. Dengan cara ini, Anda tidak akan mendapatkan coroutine yang tidak dapat ditampilkan lagi.
private var viewModelJob = Job()
  1. Di akhir isi class, ganti onCleared() dan batalkan semua coroutine. Saat ViewModel dihancurkan, onCleared() akan dipanggil.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Tepat di bawah definisi viewModelJob, tentukan uiScope untuk coroutine. Cakupan menentukan thread yang akan menjalankan coroutine, dan cakupan juga harus mengetahui tentang tugas tersebut. Untuk mendapatkan cakupan, minta instance CoroutineScope, serta teruskan dispatcher dan tugas.

Menggunakan Dispatchers.Main berarti coroutine yang diluncurkan dalam uiScope akan berjalan di thread utama. Hal ini logis untuk banyak coroutine yang dimulai dengan ViewModel, karena setelah coroutine ini melakukan beberapa pemrosesan, coroutine akan menghasilkan update UI.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Di bawah definisi uiScope, tentukan variabel yang disebut tonight untuk menampung malam saat ini. Buat variabel MutableLiveData, karena Anda harus dapat mengamati data dan mengubahnya.
private var tonight = MutableLiveData<SleepNight?>()
  1. Untuk menginisialisasi variabel tonight sesegera mungkin, buat blok init di bawah definisi tonight dan panggil initializeTonight(). Anda menentukan initializeTonight() di langkah berikutnya.
init {
   initializeTonight()
}
  1. Di bawah blok init, implementasikan initializeTonight(). Di uiScope, luncurkan coroutine. Di dalam, dapatkan nilai untuk tonight dari database dengan memanggil getTonightFromDatabase(), dan tetapkan nilai ke tonight.value. Anda menentukan getTonightFromDatabase() di langkah berikutnya.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Implementasikan getTonightFromDatabase(). Tentukan sebagai fungsi private suspend yang menampilkan SleepNight nullable, jika tidak ada SleepNight yang dimulai saat ini. Tindakan ini akan menyebabkan error karena fungsi harus menampilkan sesuatu.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. Di dalam isi fungsi getTonightFromDatabase(), tampilkan hasil dari coroutine yang berjalan dalam konteks Dispatchers.IO. Gunakan dispatcher I/O, karena mendapatkan data dari database adalah operasi I/O dan tidak ada hubungannya dengan UI.
  return withContext(Dispatchers.IO) {}
  1. Di dalam blok kembali, biarkan coroutine berjalan malam ini (malam terbaru) dari database. Jika waktu mulai dan berakhir tidak sama, yang berarti malam sudah selesai, tampilkan null. Jika tidak, kembalikan malam.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

Fungsi penangguhan getTonightFromDatabase() yang sudah selesai akan terlihat seperti ini. Tidak akan ada lagi error.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Langkah 2: Menambahkan pengendali klik untuk tombol Start

Sekarang Anda dapat menerapkan onStartTracking(), pengendali klik untuk tombol Start. Anda perlu membuat SleepNight baru, menyisipkannya ke dalam database, dan menetapkannya ke tonight. Struktur onStartTracking() akan terlihat seperti initializeTonight().

  1. Mulai dengan definisi fungsi untuk onStartTracking(). Anda dapat menempatkan pengendali klik di atas onCleared() dalam file SleepTrackerViewModel.
fun onStartTracking() {}
  1. Di dalam onStartTracking(), luncurkan coroutine di uiScope, karena Anda memerlukan hasil ini untuk melanjutkan dan mengupdate UI.
uiScope.launch {}
  1. Di dalam peluncuran coroutine, buat SleepNight baru, yang mencatat waktu saat ini sebagai waktu mulai.
        val newNight = SleepNight()
  1. Masih di dalam peluncuran coroutine, panggil insert() untuk menyisipkan newNight ke dalam database. Anda akan melihat error karena Anda belum menentukan fungsi penangguhan insert() ini. (Ini bukan fungsi DAO dengan nama yang sama.)
       insert(newNight)
  1. Selain itu, di dalam peluncuran coroutine, update tonight.
       tonight.value = getTonightFromDatabase()
  1. Di bawah onStartTracking(), tentukan insert() sebagai fungsi private suspend yang menggunakan SleepNight sebagai argumennya.
private suspend fun insert(night: SleepNight) {}
  1. Untuk isi insert(), luncurkan coroutine dalam konteks I/O dan masukkan malam ke dalam database dengan memanggil insert() dari DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. Di file tata letak fragment_sleep_tracker.xml, tambahkan pengendali klik untuk onStartTracking() ke start_button menggunakan keajaiban data binding yang Anda siapkan sebelumnya. Notasi fungsi @{() -> akan membuat fungsi lambda yang tidak mengambil argumen dan memanggil pengendali klik di sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Build dan jalankan aplikasi Anda. Ketuk tombol Mulai. Tindakan ini akan membuat data, tetapi Anda belum dapat melihat apa pun. Anda akan memperbaikinya nanti.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Langkah 3: Tampilkan data

Di SleepTrackerViewModel, variabel nights mereferensikan LiveData karena getAllNights() di DAO menampilkan LiveData.

Ini adalah fitur Room yang setiap kali data di database berubah, LiveData nights diupdate untuk menampilkan data terbaru. Anda tidak perlu menetapkan LiveData secara eksplisit atau mengupdatenya. Room memperbarui data agar cocok dengan database.

Namun, jika Anda menampilkan nights dalam tampilan teks, referensi objek akan ditampilkan. Untuk melihat konten objek, ubah data menjadi string berformat. Gunakan peta Transformation yang dijalankan setiap kali nights menerima data baru dari database.

  1. Buka file Util.kt dan hapus tanda komentar kode untuk definisi formatNights() dan pernyataan import terkait. Untuk menghapus tanda komentar kode di Android Studio, pilih semua kode yang ditandai dengan // dan tekan Cmd+/ atau Control+/.
  2. Perhatikan bahwa formatNights() menampilkan jenis Spanned, yang merupakan string berformat HTML.
  3. Buka strings.xml. Perhatikan penggunaan CDATA untuk memformat resource string untuk menampilkan data tidur.
  4. Buka SleepTrackerViewModel. Di class SleepTrackerViewModel, di bawah definisi uiScope, tentukan variabel yang disebut nights. Dapatkan semua malam dari database dan tetapkan ke variabel nights.
private val nights = database.getAllNights()
  1. Tepat di bawah definisi nights, tambahkan kode untuk mengubah nights menjadi nightsString. Gunakan fungsi formatNights() dari Util.kt.

    Teruskan nights ke dalam fungsi map() dari class Transformations. Untuk mendapatkan akses ke resource string, tentukan fungsi pemetaan sebagai memanggil formatNights(). Sediakan objek nights dan Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Buka file tata letak fragment_sleep_tracker.xml. Pada TextView, di properti android:text, Anda kini dapat mengganti string resource dengan referensi ke nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Buat ulang kode dan jalankan aplikasi. Semua data tidur Anda dengan waktu mulai akan ditampilkan sekarang.
  2. Ketuk tombol Start beberapa kali lagi, dan Anda akan melihat lebih banyak data.

Di langkah berikutnya, Anda mengaktifkan fungsi untuk tombol Stop.

Langkah 4: Menambahkan pengendali klik untuk tombol Stop

Dengan menggunakan pola yang sama seperti pada langkah sebelumnya, implementasikan pengendali klik untuk tombol Stop di SleepTrackerViewModel.

  1. Tambahkan onStopTracking() ke ViewModel. Luncurkan coroutine di uiScope. Jika waktu berakhir belum ditetapkan, tetapkan endTimeMilli ke waktu sistem saat ini dan panggil update() dengan data malam.

    Di Kotlin, sintaksis return@label menentukan fungsi tempat pernyataan ini ditampilkan, di antara beberapa fungsi bertingkat.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Terapkan update() menggunakan pola yang sama seperti yang Anda gunakan untuk mengimplementasikan insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Untuk menghubungkan pengendali klik ke UI, buka file tata letak fragment_sleep_tracker.xml dan tambahkan pengendali klik ke stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Build dan jalankan aplikasi Anda.
  2. Ketuk Mulai, lalu ketuk Hentikan. Anda melihat waktu mulai, waktu berakhir, kualitas tidur tanpa nilai, dan waktu tidur.

Langkah 5: Tambahkan pengendali klik untuk tombol Hapus

  1. Demikian pula, implementasikan onClear() dan clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Untuk menghubungkan pengendali klik ke UI, buka fragment_sleep_tracker.xml dan tambahkan pengendali klik ke clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Build dan jalankan aplikasi Anda.
  2. Ketuk Hapus untuk menghapus semua data. Lalu, ketuk Mulai dan Hentikan untuk membuat data baru.

Project Android Studio: TrackMySleepQualityCoroutines

  • Gunakan ViewModel, ViewModelFactory, dan data binding untuk menyiapkan arsitektur UI untuk aplikasi.
  • Agar UI berjalan lancar, gunakan coroutine untuk tugas yang berjalan lama, seperti semua operasi database.
  • Coroutine bersifat asinkron dan tidak memblokir. Coroutine menggunakan fungsi suspend untuk membuat kode asinkron berurutan.
  • Jika coroutine memanggil fungsi yang ditandai dengan suspend, fungsi ini akan menangguhkan eksekusi hingga hasilnya siap, bukan memblokir hingga fungsi tersebut kembali seperti panggilan fungsi normal. Kemudian, tugas akan dilanjutkan dari hasil sebelumnya.
  • Perbedaan antara pemblokiran dan penangguhan adalah jika thread diblokir, tidak ada pekerjaan lain yang terjadi. Jika thread ditangguhkan, pekerjaan lain akan terjadi hingga hasilnya tersedia.

Untuk meluncurkan coroutine, Anda memerlukan tugas, dispatcher, dan cakupan:

  • Pada dasarnya, tugas adalah apa pun yang dapat dibatalkan. Setiap coroutine memiliki tugas, dan Anda dapat menggunakan tugas untuk membatalkan coroutine.
  • dispatcher mengirimkan coroutine untuk dijalankan di berbagai thread. Dispatcher.Main menjalankan tugas pada thread utama, dan Dispartcher.IO untuk memindahkan tugas I/O yang memblokir ke kumpulan thread bersama.
  • Cakupan menggabungkan informasi, termasuk tugas dan dispatcher, untuk menentukan konteks tempat coroutine berjalan. Cakupan melacak coroutine.

Untuk menerapkan pengendali klik yang memicu operasi database, ikuti pola berikut:

  1. Meluncurkan coroutine yang berjalan di thread utama atau UI thread, karena hasilnya memengaruhi UI.
  2. Panggil fungsi penangguhan untuk melakukan pekerjaan yang berjalan lama, sehingga Anda tidak memblokir UI thread selagi menunggu hasilnya.
  3. Pekerjaan yang berjalan lama tidak ada hubungannya dengan UI, jadi beralihlah ke konteks I/O. Dengan begitu, pekerjaan dapat berjalan di kumpulan thread yang dioptimalkan dan disisihkan untuk jenis operasi ini.
  4. Lalu panggil fungsi database untuk melakukan pekerjaan.

Gunakan peta Transformations untuk membuat string dari objek LiveData setiap kali objek berubah.

Kursus Udacity:

Dokumentasi Developer Android:

Dokumentasi dan artikel lainnya:

Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Terserah instruktur untuk melakukan hal berikut:

  • Tugaskan pekerjaan rumah jika diperlukan.
  • Berkomunikasi dengan siswa cara mengirimkan tugas pekerjaan rumah.
  • Beri nilai tugas pekerjaan rumah.

Instruktur dapat menggunakan saran ini sesedikit atau sebanyak yang mereka inginkan, dan harus bebas memberikan pekerjaan rumah lain yang dirasa sesuai.

Jika Anda mengerjakan codelab ini sendiri, silakan gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.

Jawab pertanyaan ini

Pertanyaan 1

Manakah dari berikut ini yang merupakan keuntungan coroutine:

  • Iklan ini tidak memblokir
  • Keduanya berjalan secara asinkron.
  • Dapat dijalankan di thread selain thread utama.
  • Selalu membuat aplikasi berjalan lebih cepat.
  • Mereka dapat menggunakan pengecualian.
  • Dapat ditulis dan dibaca sebagai kode linier.

Pertanyaan 2

Apa yang dimaksud dengan fungsi penangguhan?

  • Fungsi biasa yang dianotasi dengan kata kunci suspend.
  • Fungsi yang dapat dipanggil di dalam coroutine.
  • Saat fungsi penangguhan sedang berjalan, thread panggilan akan ditangguhkan.
  • Fungsi penangguhan harus selalu dijalankan di latar belakang.

Pertanyaan 3

Apa perbedaan antara memblokir dan menangguhkan rangkaian pesan? Tandai semua yang benar.

  • Jika eksekusi diblokir, tugas lain tidak dapat dijalankan di thread yang diblokir.
  • Saat eksekusi ditangguhkan, thread dapat melakukan pekerjaan lain selagi menunggu pekerjaan yang dialihkan.
  • Penangguhan lebih efisien, karena thread mungkin tidak menunggu, tidak melakukan apa-apa.
  • Baik diblokir atau ditangguhkan, eksekusi masih menunggu hasil coroutine sebelum melanjutkan.

Memulai pelajaran berikutnya: 6.3 Menggunakan LiveData untuk mengontrol status tombol

Untuk link ke codelab lainnya dalam kursus ini, lihat halaman landing codelab Dasar-Dasar Kotlin Android.