Dasar-Dasar Android Kotlin 06.2: Coroutine dan Room

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

Pengantar

Salah satu prioritas utama untuk menciptakan pengalaman pengguna yang sempurna bagi aplikasi Anda adalah memastikan UI selalu responsif dan berjalan 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 aplikasi TrackMySleepQuality yang berinteraksi dengan pengguna, menggunakan coroutine Kotlin untuk melakukan operasi database dari thread utama.

Yang harus sudah Anda ketahui

Anda harus memahami:

  • Membangun antarmuka pengguna (UI) dasar menggunakan aktivitas, fragmen, tampilan, dan pengendali klik.
  • Menavigasi antar-fragmen, dan menggunakan safeArgs untuk meneruskan data sederhana antar-fragmen.
  • Model tampilan, factory model tampilan, transformasi, dan LiveData.
  • Cara membuat database Room, membuat DAO, dan menentukan entity.
  • Akan sangat membantu jika Anda memahami konsep threading dan pemrosesan multiproses.

Yang akan Anda pelajari

  • Cara kerja thread di Android.
  • Cara menggunakan coroutine Kotlin untuk memindahkan operasi database dari thread utama.
  • Cara menampilkan data yang diformat dalam 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 membuat model tampilan, coroutine, dan bagian tampilan data aplikasi TrackMySleepQuality.

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

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

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

Alur pengguna adalah sebagai berikut:

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

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

  • Pengontrol UI
  • Lihat model dan LiveData
  • Database Room

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

Anda dapat melanjutkan dengan aplikasi TrackMySleepQuality yang Anda buat 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. Build dan jalankan aplikasi. Aplikasi menampilkan UI untuk fragmen SleepTrackerFragment, tetapi tidak ada data. Tombol tidak merespons ketukan.

Langkah 2: Periksa kode

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

  1. Buka res/layout/activity_main.xml. Tata letak ini berisi fragmen nav_host_fragment. Perhatikan juga tag <merge>.

    Tag merge dapat digunakan untuk menghilangkan tata letak yang berlebihan saat menyertakan tata letak, dan sebaiknya gunakan tag ini. Contoh tata letak yang berlebihan adalah ConstraintLayout > LinearLayout > TextView, di mana 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. Di folder layout, klik dua kali fragmen pelacak tidur untuk melihat tata letak XML-nya. Perhatikan catatan berikut:
  • Data tata letak digabungkan dalam elemen <layout> untuk mengaktifkan data binding.
  • ConstraintLayout dan tampilan lainnya disusun di dalam elemen <layout>.
  • File memiliki tag placeholder <data>.

Aplikasi starter juga menyediakan dimensi, warna, dan gaya visual untuk UI. Aplikasi berisi database Room, DAO, dan entity SleepNight. Jika Anda belum menyelesaikan codelab sebelumnya, pastikan Anda mempelajari sendiri aspek kode ini.

Setelah memiliki database dan UI, Anda perlu mengumpulkan data, menambahkan data ke database, dan menampilkan data. Semua pekerjaan ini dilakukan di 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. Di paket sleeptracker, buka SleepTrackerViewModel.kt.
  2. Periksa class SleepTrackerViewModel, yang disediakan untuk Anda di aplikasi awal dan juga ditampilkan di bawah. Perhatikan bahwa class memperluas AndroidViewModel(). Class ini sama dengan ViewModel, tetapi menggunakan konteks aplikasi sebagai parameter dan menyediakannya sebagai properti. Anda akan membutuhkannya 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 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 mengambil argumen yang sama dengan ViewModel dan memperluas ViewModelProvider.Factory.
  • Di dalam factory, kode menggantikan create(), yang mengambil jenis class apa pun sebagai argumen dan menampilkan ViewModel.
  • Di bagian isi create(), kode memeriksa apakah ada class SleepTrackerViewModel yang tersedia, dan jika ada, menampilkan instance-nya. Jika tidak, kode akan menampilkan pengecualian.

Langkah 3: Perbarui SleepTrackerFragment

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

    Fungsi Kotlin requireNotNull akan memunculkan IllegalArgumentException jika nilai adalah null.
val application = requireNotNull(this.activity).application
  1. Anda memerlukan referensi ke sumber data 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 perlu meneruskan dataSource dan application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Setelah memiliki pabrik, dapatkan referensi ke SleepTrackerViewModel. Parameter SleepTrackerViewModel::class.java mengacu pada class Java runtime dari 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

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


Dalam 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. Masukkan 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 bangun ulang project untuk menghilangkan error.
  2. Terakhir, seperti biasa, pastikan kode Anda di-build dan dijalankan tanpa error.

Di Kotlin, coroutine adalah cara untuk menangani tugas yang berjalan lama secara 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: mereka 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. Hal ini dapat dilakukan secara paralel atau di prosesor terpisah. Bisa juga saat aplikasi lainnya menunggu input, Anda melakukan sedikit pemrosesan. Salah satu aspek penting dari async adalah Anda tidak dapat mengharapkan hasil tersedia, hingga Anda secara eksplisit menunggunya.

Misalnya, Anda memiliki pertanyaan yang memerlukan riset, dan Anda meminta rekan kerja untuk menemukan jawabannya. Mereka akan pergi dan mengerjakannya, yang seperti mereka melakukan pekerjaan "secara asinkron" dan "di thread terpisah". Anda dapat melanjutkan pekerjaan lain yang tidak bergantung pada jawaban, hingga rekan kerja Anda kembali dan memberi tahu Anda jawabannya.

Coroutine tidak memblokir.

Tidak memblokir berarti coroutine tidak memblokir thread utama atau UI. Jadi, dengan coroutine, pengguna selalu mendapatkan pengalaman yang paling lancar, karena interaksi UI selalu diprioritaskan.

Coroutine menggunakan fungsi penangguhan untuk membuat kode asinkron berurutan.

Kata kunci suspend adalah cara Kotlin menandai fungsi, atau jenis fungsi, sebagai tersedia untuk coroutine. Saat 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 akan dilanjutkan dari tempat terakhir, dengan hasilnya.

Saat coroutine ditangguhkan dan menunggu hasil, coroutine akan membuka blokir thread yang sedang berjalan. Dengan demikian, fungsi atau coroutine lain dapat berjalan.

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

Untuk menggunakan coroutine di Kotlin, Anda memerlukan tiga hal:

  • Pekerjaan
  • Dispatcher
  • Cakupan

Tugas: Pada dasarnya, tugas adalah apa pun yang dapat dibatalkan. Setiap coroutine memiliki tugas, dan Anda dapat menggunakan tugas tersebut untuk membatalkan coroutine. Tugas dapat diatur ke dalam hierarki induk-turunan. Membatalkan tugas induk akan segera membatalkan semua turunan tugas, yang jauh lebih mudah daripada membatalkan setiap coroutine secara manual.

Dispatcher: Dispatcher mengirimkan coroutine untuk dijalankan di berbagai thread. Misalnya, Dispatcher.Main menjalankan tugas di thread utama, dan Dispatcher.IO melimpahkan tugas blocking I/O ke kumpulan thread bersama.

Cakupan: Cakupan coroutine menentukan konteks tempat coroutine berjalan. Cakupan menggabungkan informasi tentang tugas dan dispatcher coroutine. Cakupan melacak coroutine. Saat Anda meluncurkan coroutine, coroutine tersebut "berada dalam cakupan", yang berarti Anda telah menunjukkan cakupan mana yang akan melacak coroutine.

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

  • Saat pengguna mengetuk tombol Mulai, aplikasi akan membuat malam tidur baru dan menyimpan malam tidur tersebut di database.
  • Saat pengguna mengetuk tombol Berhenti, 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: Siapkan coroutine untuk operasi database

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

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

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

    $coroutine_version ditentukan dalam file build.gradle project 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. Di bagian isi class, tentukan viewModelJob dan tetapkan instance Job ke dalamnya. viewModelJob ini memungkinkan Anda membatalkan semua coroutine yang dimulai oleh model tampilan ini saat model tampilan tidak lagi digunakan dan dihapus. Dengan cara ini, Anda tidak akan memiliki coroutine yang tidak dapat kembali ke tempat semula.
private var viewModelJob = Job()
  1. Di akhir isi class, ganti onCleared() dan batalkan semua coroutine. Saat ViewModel dihancurkan, onCleared() dipanggil.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Tepat di bawah definisi viewModelJob, tentukan uiScope untuk coroutine. Cakupan menentukan thread tempat coroutine akan berjalan, dan cakupan juga perlu mengetahui tugas. Untuk mendapatkan cakupan, minta instance CoroutineScope, dan teruskan dispatcher dan tugas.

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

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Di bawah definisi uiScope, tentukan variabel yang disebut tonight untuk menyimpan malam saat ini. Buat variabel MutableLiveData, karena Anda harus dapat mengamati dan mengubah data.
private var tonight = MutableLiveData<SleepNight?>()
  1. Untuk melakukan inisialisasi variabel tonight sesegera mungkin, buat blok init di bawah definisi tonight dan panggil initializeTonight(). Anda akan menentukan initializeTonight() di langkah berikutnya.
init {
   initializeTonight()
}
  1. Di bawah blok init, terapkan initializeTonight(). Di uiScope, luncurkan coroutine. Di dalamnya, dapatkan nilai untuk tonight dari database dengan memanggil getTonightFromDatabase(), lalu tetapkan nilai ke tonight.value. Anda akan menentukan getTonightFromDatabase() di langkah berikutnya.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Menerapkan getTonightFromDatabase(). Tentukan sebagai fungsi private suspend yang menampilkan SleepNight yang dapat bernilai null, jika tidak ada SleepNight yang saat ini dimulai. Hal ini akan menimbulkan 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 return, biarkan coroutine mendapatkan malam ini (malam terbaru) dari database. Jika waktu mulai dan akhir tidak sama, yang berarti malam telah selesai, kembalikan null. Jika tidak, tampilkan 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: Tambahkan pengendali klik untuk tombol Mulai

Sekarang Anda dapat menerapkan onStartTracking(), pengendali klik untuk tombol Start. Anda perlu membuat SleepNight baru, memasukkannya ke dalam database, dan menetapkannya ke tonight. Struktur onStartTracking() akan sangat mirip dengan 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 memperbarui 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. Juga di dalam peluncuran coroutine, perbarui 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. Dalam 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 @{() -> membuat fungsi lambda yang tidak menggunakan argumen dan memanggil pengendali klik di sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Build dan jalankan aplikasi Anda. Ketuk tombol Start. Tindakan ini 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 dalam database berubah, LiveData nights akan diperbarui untuk menampilkan data terbaru. Anda tidak perlu menetapkan LiveData secara eksplisit atau memperbaruinya. Room memperbarui data agar sesuai dengan database.

Namun, jika Anda menampilkan nights dalam tampilan teks, referensi objek akan ditampilkan. Untuk melihat isi 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 komentar kode untuk definisi formatNights() dan pernyataan import terkait. Untuk menghapus 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 guna 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 fungsi map() dari class Transformations. Untuk mendapatkan akses ke resource string Anda, tentukan fungsi pemetaan sebagai panggilan formatNights(). Berikan nights dan objek Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Buka file tata letak fragment_sleep_tracker.xml. Di TextView, di properti android:text, Anda kini dapat mengganti string resource dengan referensi ke nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Bangun kembali kode Anda dan jalankan aplikasi. Semua data tidur Anda dengan waktu mulai akan ditampilkan sekarang.
  2. Ketuk tombol Mulai beberapa kali lagi, dan Anda akan melihat lebih banyak data.

Pada langkah berikutnya, Anda akan mengaktifkan fungsi tombol Stop.

Langkah 4: Tambahkan pengendali klik untuk tombol Berhenti

Dengan menggunakan pola yang sama seperti pada langkah sebelumnya, terapkan 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 dari 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 menerapkan 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. Bangun dan jalankan aplikasi Anda.
  2. Ketuk Mulai, lalu ketuk Berhenti. Anda akan melihat waktu mulai, waktu berakhir, kualitas tidur tanpa nilai, dan waktu tidur.

Langkah 5: Tambahkan pengendali klik untuk tombol Hapus

  1. Demikian pula, terapkan 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. Bangun dan jalankan aplikasi Anda.
  2. Ketuk Hapus untuk menghapus semua data. Kemudian, ketuk Mulai dan Berhenti untuk membuat data baru.

Project Android Studio: TrackMySleepQualityCoroutines

  • Gunakan ViewModel, ViewModelFactory, dan data binding untuk menyiapkan arsitektur UI 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.
  • Saat coroutine memanggil fungsi yang ditandai dengan suspend, daripada memblokir hingga fungsi tersebut kembali seperti panggilan fungsi normal, fungsi tersebut akan menunda eksekusi hingga hasilnya siap. Kemudian, fungsi akan dilanjutkan dari bagian terakhir dengan hasil tersebut.
  • Perbedaan antara pemblokiran dan penangguhan adalah jika thread diblokir, tidak ada tugas 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 di thread utama, dan Dispartcher.IO digunakan untuk melimpahkan tugas blocking I/O ke kumpulan thread bersama.
  • Scope 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 ini:

  1. Luncurkan 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 thread UI saat menunggu hasilnya.
  3. Tugas yang berjalan lama tidak ada hubungannya dengan UI, jadi beralihlah ke konteks I/O. Dengan begitu, tugas dapat berjalan di kumpulan thread yang dioptimalkan dan dicadangkan untuk jenis operasi ini.
  4. Kemudian, 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. Instruktur menentukan hal berikut:

  • Memberikan pekerjaan rumah jika diperlukan.
  • Memberi tahu siswa cara mengirimkan tugas pekerjaan rumah.
  • Memberi nilai tugas pekerjaan rumah.

Instruktur bisa menggunakan saran ini sesuai kebutuhan, dan bebas menugaskan pekerjaan rumah lain yang dirasa cocok.

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

Jawab pertanyaan berikut

Pertanyaan 1

Manakah dari berikut ini yang merupakan keunggulan coroutine:

  • Tidak memblokir
  • Keduanya berjalan secara asinkron.
  • Dapat dijalankan di thread selain thread utama.
  • Selalu membuat aplikasi berjalan lebih cepat.
  • Dapat menggunakan pengecualian.
  • Dapat ditulis dan dibaca sebagai kode linear.

Pertanyaan 2

Apa itu fungsi penangguhan?

  • Fungsi biasa yang dianotasi dengan kata kunci suspend.
  • Fungsi yang dapat dipanggil di dalam coroutine.
  • Saat fungsi penangguhan berjalan, thread pemanggil ditangguhkan.
  • Fungsi penangguhan harus selalu berjalan 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 pun.
  • Baik diblokir atau ditangguhkan, eksekusi masih menunggu hasil coroutine sebelum dilanjutkan.

Mulai pelajaran berikutnya: 6.3 Menggunakan LiveData untuk mengontrol status tombol

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