Menangani Proto DataStore

Apa itu DataStore?

DataStore adalah solusi penyimpanan data baru dan telah disempurnakan yang ditujukan untuk mengganti SharedPreferences. Dibangun pada Flow dan coroutine Kotlin, DataStore menyediakan dua implementasi yang berbeda: Proto DataStore yang memungkinkan menyimpan objek yang diketik (didukung oleh buffering protokol) dan Preference DataStore yang menyimpan pasangan nilai kunci. Data disimpan secara asinkron, konsisten, dan transaksional yang mengatasi beberapa kelemahan SharedPreferences.

Yang akan Anda pelajari

  • Pengertian DataStore dan alasan harus menggunakannya.
  • Cara menambahkan DataStore ke project Anda.
  • Perbedaan antara Preference DataStore dan Proto DataStore, serta keuntungan dari keduanya.
  • Cara menggunakan Proto DataStore.
  • Cara bermigrasi dari SharedPreferences ke Proto DataStore.

Yang akan Anda buat

Dalam codelab ini, Anda akan memulai dengan contoh aplikasi yang menampilkan daftar tugas yang dapat difilter menurut statusnya yang telah selesai, serta dapat diurutkan berdasarkan prioritas dan batas waktu.

fcb2ffa4e6b77f33.gif

Flag boolean untuk filter Tampilkan tugas yang selesai disimpan di memori. Tata urutan disimpan ke disk menggunakan objek SharedPreferences.

Karena DataStore memiliki 2 implementasi yang berbeda: Preference DataStore dan Proto DataStore, Anda akan mempelajari cara menggunakan Proto DataStore untuk menyelesaikan tugas berikut dalam setiap penerapan:

  • Mempertahankan filter status yang telah selesai di DataStore.
  • Memigrasikan tata urutan dari SharedPreferences ke DataStore.

Sebaiknya gunakan juga codelab Preference DataStore sehingga Anda memahami perbedaan antara keduanya dengan lebih baik.

Yang Anda butuhkan

Untuk pengantar Komponen Arsitektur, lihat Room dengan codelab View. Untuk pengantar Flow, lihat Coroutine Lanjutan dengan Flow Kotlin dan codelab LiveData.

Dalam langkah ini, Anda akan mendownload kode untuk seluruh codelab, lalu menjalankan aplikasi contoh sederhana.

Agar dapat segera dimulai, kami telah menyiapkan proyek pemula untuk Anda kembangkan.

Jika sudah menginstal git, Anda cukup menjalankan perintah di bawah. Untuk memeriksa apakah git sudah diinstal, ketik git --version di terminal atau command line dan verifikasi bahwa git dijalankan dengan benar.

 git clone https://github.com/googlecodelabs/android-datastore

Status awal berada di cabang master. Kode solusi terletak di cabang proto_datastore.

Jika tidak memiliki git, Anda dapat mengklik tombol berikut untuk mendownload semua kode untuk codelab ini:

Download kode sumber

  1. Buka zip kode, lalu buka project di Android Studio versi 3.6 atau yang lebih baru.
  2. Jalankan aplikasi untuk menjalankan konfigurasi di perangkat atau emulator.

b3c0dfdb92dfed77.png

Aplikasi mulai berjalan dan akan menampilkan daftar tugas:

16eb4ceb800bf131.png

Aplikasi ini memungkinkan Anda melihat daftar tugas. Setiap tugas memiliki properti berikut: nama, status selesai, prioritas, dan batas waktu.

Untuk menyederhanakan kode yang perlu dikerjakan, aplikasi hanya mengizinkan Anda melakukan dua tindakan:

  • Menampilkan atau menyembunyikan visibilitas tugas yang telah selesai - tugas disembunyikan secara default
  • Mengurutkan tugas menurut prioritas, batas waktu, atau batas waktu dan prioritas

Aplikasi mengikuti arsitektur yang direkomendasikan di "Panduan arsitektur aplikasi". Anda akan menemukan item berikut di setiap paket:

data

  • Class model Task.
  • Class TasksRepository - bertanggung jawab untuk menyediakan tugas. Untuk alasan kemudahan, class ini menampilkan data hardcode dan mengeksposnya melalui Flow untuk merepresentasikan skenario yang lebih realistis.
  • Class UserPreferencesRepository - menampung SortOrder yang didefinisikan sebagai enum. Tata urutan saat ini disimpan di SharedPreferences sebagai String, berdasarkan nama nilai enum. Ini akan memperlihatkan metode sinkron untuk menyimpan dan mendapatkan tata urutan.

ui

  • Class yang terkait menampilkan Activity dengan RecyclerView.
  • Class TasksViewModel bertanggung jawab atas logika UI.

TasksViewModel - menampung semua elemen yang diperlukan untuk mem-build data yang perlu ditampilkan di UI: daftar tugas, flag tampilkan yang selesai dan tata urutan, yang digabungkan dalam objek TasksUiModel. Setiap kali salah satu nilai ini berubah, TasksUiModel yang baru harus direkonstruksi. Untuk dapat melakukannya, 3 elemen akan digabungkan:

  • Flow<List<Task>> diambil dari TasksRepository.
  • MutableStateFlow<Boolean> yang menyimpan flag tampilkan yang selesai terbaru, yang hanya disimpan dalam memori.
  • MutableStateFlow<SortOrder> yang menyimpan nilai SortOrder terbaru.

Untuk memastikan bahwa UI telah diupdate dengan benar, LiveData<TasksUiModel> akan ditampilkan hanya saat Aktivitas dimulai.

Ada beberapa masalah dengan kode:

  • Kita memblokir UI thread pada IO disk saat menginisialisasi UserPreferencesRepository.sortOrder. Hal ini dapat mengakibatkan jank pada UI.
  • Flag tampilkan yang selesai hanya disimpan dalam memori, sehingga reset akan terjadi setiap kali pengguna membuka aplikasi. Seperti SortOrder, flag ini harus disimpan agar tetap ada saat aplikasi ditutup.
  • Saat ini kami menggunakan SharedPreferences untuk mempertahankan data, tetapi kami menyimpan MutableStateFlow di memori yang telah kami modifikasi secara manual agar dapat menerima pemberitahuan perubahan. Cara ini cenderung mudah rusak jika nilai diubah di tempat lain di aplikasi.
  • Di UserPreferencesRepository dua metode ditampilkan untuk tata urutan: enableSortByDeadline() dan enableSortByPriority(). Kedua metode ini bergantung pada nilai tata urutan saat ini, tetapi jika salah satu metode dipanggil sebelum metode lainnya selesai, maka nilai akhir salah yang akan muncul. Bahkan, metode ini bisa mengakibatkan jank pada UI dan pelanggaran Mode Ketat saat dipanggil di UI thread.

Meskipun flag tampilkan yang selesai dan tata urutan adalah preferensi pengguna, saat ini keduanya ditampilkan sebagai dua objek yang berbeda. Jadi, salah satu tujuan kami adalah menyatukan dua flag ini dalam class UserPreferences.

Mari kita cari tahu cara menggunakan DataStore untuk membantu kami menangani masalah ini.

Sering kali Anda mungkin perlu menyimpan set data yang kecil atau sederhana. Sebelumnya, Anda mungkin telah menggunakan SharedPreferences, tetapi API ini juga memiliki serangkaian kelemahan. Library Jetpack DataStore bertujuan mengatasi masalah tersebut dan membuat API yang sederhana, aman, dan asinkron untuk menyimpan data. Solusi ini menyediakan 2 implementasi yang berbeda:

  • Preferences DataStore
  • Proto DataStore

Fitur

SharedPreferences

PreferencesDataStore

ProtoDataStore

API Asinkron

✅ (hanya untuk membaca nilai yang diubah, melalui pemroses)

✅ (melalui Flow)

✅ (melalui Flow)

API Sinkron

✅ (tetapi tidak aman untuk dipanggil di UI thread)

Aman untuk dipanggil di UI thread

❌*

✅ (tugas dipindahkan ke Dispatchers.IO di balik layar)

✅ (tugas dipindahkan ke Dispatchers.IO di balik layar)

Dapat memberikan sinyal error

Aman dari pengecualian runtime

❌**

Memiliki API transaksional dengan jaminan konsistensi yang kuat

Menangani migrasi data

✅ (dari SharedPreferences)

✅ (dari SharedPreferences)

Keamanan jenis

✅ dengan Buffering Protokol

  • SharedPreferences memiliki API sinkron yang dapat terlihat aman untuk dipanggil di UI thread, tetapi sebenarnya melakukan operasi I/O disk. Selanjutnya, apply() akan memblokir UI thread di fsync(). Panggilan fsync() yang tertunda dipicu setiap kali layanan dimulai atau berhenti, dan setiap kali aktivitas dimulai atau berhenti di mana pun di aplikasi. UI thread diblokir saat panggilan fsync() tertunda yang dijadwalkan oleh apply(), dan sering menjadi sumber ANR.

** SharedPreferences menampilkan error penguraian sebagai pengecualian runtime.

Preference DataStore vs Proto DataStore

Meskipun Preference DataStore dan Proto DataStore mengizinkan penyimpanan data, keduanya dilakukan dengan cara yang berbeda:

  • Preference DataStore, seperti SharedPreferences, mengakses data berdasarkan kunci, tanpa menentukan skema awal.
  • Proto DataStore menentukan skema menggunakan Buffering protokol. Menggunakan Protobuf memungkinkan untuk mempertahankan data terketik dengan kuat. Protobuf ini lebih cepat, lebih kecil, lebih sederhana, dan tidak terlalu ambigu dibandingkan dengan XML dan format data serupa lainnya. Meskipun Proto DataStore mengharuskan Anda mempelajari mekanisme serialisasi baru, kami yakin bahwa manfaat yang diketik dengan kuat yang diberikan oleh Proto DataStore sangat berharga.

Room vs DataStore

Jika memerlukan update sebagian, integritas referensial, atau set data yang besar/kompleks, sebaiknya gunakan Room, bukan DataStore. DataStore cocok untuk set data kecil yang sederhana dan tidak mendukung update sebagian atau integritas referensial.

Salah satu kelemahan dari SharedPreferences dan Preference DataStore adalah tidak ada cara untuk menentukan skema atau memastikan bahwa kunci diakses menggunakan jenis yang benar. Proto DataStore mengatasi masalah ini menggunakan Buffering protokol untuk menentukan skema. Dengan menggunakan proto, DataStore tahu jenis yang disimpan dan akan menyediakannya, sehingga tidak perlu menggunakan kunci.

Mari kita lihat cara menambahkan Proto DataStore dan Protobuf ke project, pengertian dari Buffering protokol, dan cara menggunakannya dengan Proto DataStore serta cara memigrasikan SharedPreferences ke DataStore.

Menambahkan dependensi

Untuk menggunakan Proto DataStore dan mendapatkan Protobuf untuk membuat kode skema, beberapa perubahan pada file build.gradle harus dibuat:

  • Menambahkan plugin Protobuf
  • Menambahkan dependensi Protobuf dan Proto DataStore
  • Mengonfigurasi Protobuf
plugins {
    ...
    id "com.google.protobuf" version "0.8.12"
}

dependencies {
    implementation  "androidx.datastore:datastore-core:1.0.0-alpha04"
    implementation  "com.google.protobuf:protobuf-javalite:3.10.0"
    ...
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.0"
    }

    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    // for more information.
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}

Buffering protokol adalah mekanisme untuk melakukan serialisasi data terstruktur. Anda menentukan cara pembuatan struktur data satu kali, lalu compiler membuat kode sumber untuk menulis dan membaca data terstruktur dengan mudah.

Membuat file proto

Anda menentukan skema dalam file proto. Dalam codelab, kami memiliki 2 preferensi pengguna: tampilkan yang selesai dan tata urutan; saat ini keduanya diwakili sebagai dua objek yang berbeda. Jadi, salah satu sasaran kita adalah menggabungkan dua flag ini dalam class UserPreferences yang disimpan di DataStore. Daripada menentukan class ini di Kotlin, class ini akan didefinisikan dalam skema Protobuf.

Lihat Panduan bahasa proto untuk mengetahui info sintaksis yang lebih lengkap. Dalam codelab ini, kita hanya akan berfokus pada jenis yang dibutuhkan.

Buat file baru bernama user_prefs.proto di direktori app/src/main/proto. Jika Anda tidak melihat struktur folder ini, beralih ke Tampilan project. Dalam Protobuf, setiap struktur ditentukan menggunakan kata kunci message dan setiap anggota struktur ditentukan di dalam pesan, berdasarkan jenis dan nama, dan akan diberi urutan berdasarkan angka 1. Mari kita tentukan pesan UserPreferences yang, untuk saat ini, hanya memiliki nilai (logika) boolean yang disebut showCompleted.

syntax = "proto3";

option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message UserPreferences {
  // filter for showing / hiding completed tasks
  bool show_completed = 1;
}

Membuat penserialisasi

Untuk memberi tahu DataStore cara membaca dan menulis jenis data yang telah ditentukan dalam file proto, kita perlu menerapkan Penserialisasi. Penserialisasi juga menentukan nilai default yang akan ditampilkan jika tidak ada data pada disk. Buat file baru bernama UserPreferencesSerializer dalam paket data:

object UserPreferencesSerializer : Serializer<UserPreferences> {
    override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
    override fun readFrom(input: InputStream): UserPreferences {
        try {
            return UserPreferences.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}

Membuat DataStore

Flag tampilkan yang selesai disimpan di memori, di TasksViewModel. Mari kita membuat kolom pribadi DataStore<UserPreferences> di UserPreferencesRepository, berdasarkan metode ekstensi Context.createDataStore(). Metode ini memiliki dua parameter wajib:

  • Nama file yang akan ditindaklanjuti oleh DataStore.
  • Penserialisasi untuk jenis yang digunakan dengan DataStore. Dalam kasus kami: UserPreferencesSerializer
private val dataStore: DataStore<UserPreferences> =
    context.createDataStore(
        fileName = "user_prefs.pb",
        serializer = UserPreferencesSerializer)

Membaca data dari Proto DataStore

Proto DataStore mengekspos data yang tersimpan di Flow<UserPreferences>. Mari kita buat nilai userPreferencesFlow: Flow<UserPreferences> publik yang ditetapkan ke dataStore.data:

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data

Menangani pengecualian saat membaca data

Saat DataStore membaca data dari file, IOException akan muncul saat terjadi error selama pembacaan data. Kita dapat mengatasinya dengan menggunakan transformasi Flow catch dan hanya mencatat error:

private val TAG: String = "UserPreferencesRepo"

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->
        // dataStore.data throws an IOException when an error is encountered when reading data
        if (exception is IOException) {
            Log.e(TAG, "Error reading sort order preferences.", exception)
            emit(UserPreferences.getDefaultInstance())
        } else {
            throw exception
        }
    }

Menulis data ke Proto DataStore

Untuk menulis data, DataStore menawarkan fungsi DataStore.updateData() yang ditangguhkan dengan parameter status saat ini dari UserPreferences. Untuk mengupdatenya, kita harus mengubah objek preferensi menjadi builder, menetapkan nilai baru, lalu membuat preferensi baru.

updateData() mengupdate data secara transaksional dalam operasi baca-tulis-modifikasi yang sederhana. Coroutine akan selesai setelah data disimpan di disk.

Mari kita buat fungsi penangguhan yang memungkinkan untuk mengupdate properti UserPreferences, yang disebut updateShowCompleted(), yang memanggil dataStore.updateData() dan menetapkan nilai baru:

suspend fun updateShowCompleted(completed: Boolean) {
    dataStore.updateData { preferences ->
        preferences.toBuilder().setShowCompleted(completed).build()
    }
}

Pada tahap ini, aplikasi harus dikompilasi tetapi fungsi yang baru saja dibuat di UserPreferencesRepository tidak akan digunakan.

Menentukan data yang akan disimpan di proto

Tata urutan disimpan di SharedPreferences. Mari kita pindahkan ke DataStore. Untuk melakukannya, mulai dengan mengupdate UserPreferences di file proto untuk menyimpan urutan penyortiran. Karena urutan penyortirannya adalah enum, kita harus menentukannya di UserPreference. enums ditentukan dalam protobuf yang mirip dengan Kotlin.

Untuk enumerasi, nilai default adalah nilai pertama yang tercantum dalam definisi jenis enum. Namun, saat bermigrasi dari SharedPreferences, kita perlu mengetahui apakah nilai yang telah didapatkan adalah nilai default atau nilai yang sebelumnya ditetapkan di SharedPreferences. Untuk memudahkannya, kami menetapkan nilai baru ke enum SortOrder: UNSPECIFIED dan mencantumkannya terlebih dahulu sehingga nilai baru ini dapat menjadi nilai default.

File user_prefs.proto akan terlihat seperti ini:

syntax = "proto3";

option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message UserPreferences {
  // filter for showing / hiding completed tasks
  bool show_completed = 1;

  // defines tasks sorting order: no order, by deadline, by priority, by deadline and priority
  enum SortOrder {
    UNSPECIFIED = 0;
    NONE = 1;
    BY_DEADLINE = 2;
    BY_PRIORITY = 3;
    BY_DEADLINE_AND_PRIORITY = 4;
  }

  // user selected tasks sorting order
  SortOrder sort_order = 2;
}

Bersihkan dan lakukan build ulang pada project Anda untuk memastikan bahwa objek UserPreferences baru telah dibuat dan berisi kolom baru.

Setelah SortOrder ditetapkan dalam file proto, kita dapat menghapus deklarasi dari UserPreferencesRepository. Hapus:

enum class SortOrder {
    NONE,
    BY_DEADLINE,
    BY_PRIORITY,
    BY_DEADLINE_AND_PRIORITY
}

Pastikan impor SortOrder yang tepat digunakan di mana pun:

import com.codelab.android.datastore.UserPreferences.SortOrder

Di TasksViewModel.filterSortTasks(), kami melakukan berbagai tindakan berdasarkan jenis SortOrder. Setelah menambahkan opsi UNSPECIFIED, kita harus menambahkan kasus lain untuk pernyataan when(sortOrder). Karena kami tidak ingin menangani opsi lain selain yang ada saat ini, kami dapat memberikan UnsupportedOperationException dalam kasus lain.

Fungsi filterSortTasks() terlihat seperti ini:

private fun filterSortTasks(
    tasks: List<Task>,
    showCompleted: Boolean,
    sortOrder: SortOrder
): List<Task> {
    // filter the tasks
    val filteredTasks = if (showCompleted) {
        tasks
    } else {
        tasks.filter { !it.completed }
    }
    // sort the tasks
    return when (sortOrder) {
        SortOrder.UNSPECIFIED -> filteredTasks
        SortOrder.NONE -> filteredTasks
        SortOrder.BY_DEADLINE -> filteredTasks.sortedByDescending { it.deadline }
        SortOrder.BY_PRIORITY -> filteredTasks.sortedBy { it.priority }
        SortOrder.BY_DEADLINE_AND_PRIORITY -> filteredTasks.sortedWith(
            compareByDescending<Task> { it.deadline }.thenBy { it.priority }
        )
        // We shouldn't get any other values
        else -> throw UnsupportedOperationException("$sortOrder not supported")
    }
}

Bermigrasi dari SharedPreferences

Untuk membantu migrasi, DataStore menentukan class SharedPreferencesMigration. Mari buat di UserPreferencesRepository. Blok migrate menyediakan dua parameter:

  • SharedPreferencesView yang memungkinkan data untuk diambil dari SharedPreferences
  • Data UserPreferences saat ini

Objek UserPreferences harus ditampilkan.

Saat menerapkan blok migrate, lakukan langkah-langkah berikut:

  1. Periksa nilai sortOrder di UserPreferences.
  2. Jika ini adalah SortOrder.UNSPECIFIED, artinya nilai harus diambil dari SharedPreferences. Jika SortOrder tidak ada, SortOrder.NONE dapat digunakan sebagai default.
  3. Setelah mendapatkan urutan penyortiran, kita harus mengonversi objek UserPreferences menjadi builder, menetapkan tata urutan, lalu membuat objek lagi dengan memanggil build(). Tidak ada kolom lain yang akan terpengaruh dengan perubahan ini.
  4. Jika nilai sortOrder di UserPreferences bukanlah SortOrder.UNSPECIFIED, data saat ini yang dapat ditampilkan hanyalah data yang telah didapatkan di migrate karena migrasi pasti sudah berjalan dengan baik.
private val sharedPrefsMigration = SharedPreferencesMigration(
    context,
    USER_PREFERENCES_NAME
) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
        // Define the mapping from SharedPreferences to UserPreferences
        if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
            currentData.toBuilder().setSortOrder(
                SortOrder.valueOf(
                    sharedPrefs.getString(
                        SORT_ORDER_KEY,SortOrder.NONE.name)!!
                )
            ).build()
        } else {
            currentData
        }
    }

Setelah menentukan logika migrasi, selanjutnya DataStore harus diberi tahu bahwa data tersebut harus digunakan. Untuk itu, update builder DataStore dan tetapkan daftar baru ke parameter migrations yang berisi instance SharedPreferencesMigration:

private val dataStore: DataStore<UserPreferences> = context.createDataStore(
    fileName = "user_prefs.pb",
    serializer = UserPreferencesSerializer,
    migrations = listOf(sharedPrefsMigration)
)

Menyimpan urutan penyortiran ke DataStore

Untuk mengupdate urutan penyortiran jika enableSortByDeadline() dan enableSortByPriority() dipanggil, lakukan hal berikut:

  • Panggil fungsi masing-masing di lambda dataStore.updateData().
  • Karena updateData() adalah fungsi penangguhan, enableSortByDeadline() dan enableSortByPriority() juga harus dibuat sebagai fungsi penangguhan.
  • Gunakan UserPreferences saat ini yang diterima dari updateData() untuk membuat urutan penyortiran baru
  • Update UserPreferences dengan mengonversinya menjadi builder, menetapkan urutan penyortiran baru, lalu melakukan update lagi pada preferensi.

Penerapan enableSortByDeadline() akan terlihat seperti ini. Anda dapat melakukan perubahan untuk enableSortByPriority() sendiri.

suspend fun enableSortByDeadline(enable: Boolean) {
    // updateData handles data transactionally, ensuring that if the sort is updated at the same
    // time from another thread, we won't have conflicts
    dataStore.updateData { preferences ->
        val currentOrder = preferences.sortOrder
        val newSortOrder =
            if (enable) {
                if (currentOrder == SortOrder.BY_PRIORITY) {
                    SortOrder.BY_DEADLINE_AND_PRIORITY
                } else {
                    SortOrder.BY_DEADLINE
                }
            } else {
                if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
                    SortOrder.BY_PRIORITY
                } else {
                    SortOrder.NONE
                }
            }
        preferences.toBuilder().setSortOrder(newSortOrder).build()
    }
}

Setelah UserPreferencesRepository menyimpan flag tampilkan yang selesai dan tata urutan di DataStore dan mengekspos Flow<UserPreferences>, update TasksViewModel untuk menggunakannya.

Hapus showCompletedFlow dan sortOrderFlow, lalu buat nilai yang disebut userPreferencesFlow yang diinisialisasi dengan userPreferencesRepository.userPreferencesFlow:

private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow

Pada pembuatan tasksUiModelFlow, ganti showCompletedFlow dan sortOrderFlow dengan userPreferencesFlow. Ganti parameter yang sesuai.

Saat memanggil filterSortTasks, teruskan showCompleted dan sortOrder dari userPreferences. Kode akan terlihat seperti ini:

private val tasksUiModelFlow = combine(
        repository.tasks,
        userPreferencesFlow
    ) { tasks: List<Task>, userPreferences: UserPreferences ->
        return@combine TasksUiModel(
            tasks = filterSortTasks(
                tasks,
                userPreferences.showCompleted,
                userPreferences.sortOrder
            ),
            showCompleted = userPreferences.showCompleted,
            sortOrder = userPreferences.sortOrder
        )
    }

Fungsi showCompletedTasks() kini harus diupdate agar dapat memanggil userPreferencesRepository.updateShowCompleted(). Karena ini adalah fungsi penangguhan, buat coroutine baru dalam viewModelScope:

fun showCompletedTasks(show: Boolean) {
    viewModelScope.launch {
        userPreferencesRepository.updateShowCompleted(show)
    }
}

Fungsi userPreferencesRepository enableSortByDeadline() dan enableSortByPriority() kini menjadi fungsi penangguhan sehingga juga harus dipanggil dalam coroutine baru yang diluncurkan di viewModelScope:

fun enableSortByDeadline(enable: Boolean) {
    viewModelScope.launch {
       userPreferencesRepository.enableSortByDeadline(enable)
    }
}

fun enableSortByPriority(enable: Boolean) {
    viewModelScope.launch {
        userPreferencesRepository.enableSortByPriority(enable)
    }
}

Pembersihan UserPreferencesRepository

Mari kita hapus kolom dan metode yang tidak diperlukan lagi. Anda dapat menghapus hal-hal berikut:

  • _sortOrderFlow
  • sortOrderFlow
  • updateSortOrder()
  • private val sortOrder: SortOrder
  • private val sharedPreferences

Sekarang aplikasi telah berhasil dikompilasi. Mari kita jalankan untuk melihat apakah flag tampilkan yang selesai dan tata urutan disimpan dengan benar.

Lihat cabang proto di repo codelab untuk membandingkan perubahan.

Setelah bermigrasi ke Proto DataStore, rangkum semua hal yang telah dipelajari:

  • SharedPreferences memiliki serangkaian kelemahan, mulai dari API sinkron yang bisa terlihat aman untuk dipanggil di UI thread, tidak ada mekanisme pembuatan sinyal error, kurangnya API transaksional, dan lainnya.
  • DataStore adalah pengganti SharedPreferences yang mengatasi sebagian besar kekurangan API.
  • DataStore memiliki API asinkron sepenuhnya yang menggunakan Flow dan coroutine Kotlin, menangani migrasi data, menjamin konsistensi data, dan menangani kerusakan data.