Menyimpan status UI

Panduan ini membahas harapan pengguna tentang status UI, dan opsi yang tersedia untuk mempertahankan status.

Menyimpan dan memulihkan status UI dengan cepat setelah sistem menghancurkan aktivitas host atau proses aplikasi sangat penting untuk pengalaman pengguna yang baik. Pengguna berharap status UI tetap sama, tetapi sistem mungkin menghancurkan aktivitas yang menghosting layar dan statusnya yang tersimpan.

Untuk menjembatani kesenjangan antara harapan pengguna dan perilaku sistem, gunakan kombinasi metode berikut:

Solusi yang optimal bergantung pada kompleksitas data UI, kasus penggunaan aplikasi, dan keseimbangan antara kecepatan akses data dan penggunaan memori.

Pastikan aplikasi Anda memenuhi harapan pengguna dan menawarkan antarmuka yang cepat dan responsif. Hindari penundaan saat memuat data ke dalam UI, terutama setelah perubahan konfigurasi umum seperti rotasi.

Ekspektasi pengguna dan perilaku sistem

Bergantung pada tindakan yang dilakukan oleh pengguna, mereka mengharapkan status UI dihapus atau dipertahankan. Pada beberapa kasus, sistem otomatis melakukan tindakan yang diharapkan oleh pengguna. Dalam kasus lain, sistem melakukan kebalikannya.

Penutupan status UI yang diinisialisasi pengguna

Pengguna ingin bahwa saat mereka membuka layar, status UI sementara layar tersebut tetap sama hingga mereka sepenuhnya menutupnya. Pengguna dapat menutup layar atau aplikasi sepenuhnya dengan melakukan hal berikut:

  • Menggeser aplikasi di luar layar Ringkasan (Terbaru).
  • Menutup atau memaksa keluar aplikasi dari layar Setelan.
  • Memulai ulang perangkat.
  • Menyelesaikan beberapa jenis tindakan "penyelesaian" (yang didukung oleh Activity.finish()).

Asumsi pengguna dalam kasus penutupan yang menyeluruh ini adalah bahwa pengguna menutup layar secara permanen, dan jika pengguna kembali, pengguna berharap layar dimulai dari status bersih. Perilaku sistem yang mendasari skenario penutupan ini sesuai dengan ekspektasi pengguna - instance aktivitas host akan dihancurkan dan dihapus dari memori, beserta status apa saja yang tersimpan di dalamnya dan catatan status tersimpan apa saja yang terkait dengannya.

Ada beberapa pengecualian aturan ini terkait penutupan menyeluruh—misalnya pengguna mungkin berharap browser membawa mereka ke halaman web yang mereka lihat sebelum keluar dari browser menggunakan tombol kembali.

Penutupan status UI yang dimulai oleh sistem

Pengguna berharap status UI layar tetap sama selama perubahan konfigurasi, seperti rotasi atau beralih ke mode multi-aplikasi. Namun, secara default sistem menghancurkan aktivitas host saat perubahan konfigurasi tersebut terjadi, dengan menghapus status UI apa saja yang tersimpan di dalamnya. Untuk mempelajari lebih lanjut konfigurasi perangkat, lihat Merespons perubahan konfigurasi di Jetpack Compose.

Perlu diperhatikan, Anda dapat (meski tidak direkomendasikan) mengganti perilaku default untuk perubahan konfigurasi. Lihat Menangani perubahan konfigurasi untuk mengetahui detail selengkapnya.

Pengguna juga berharap status UI aplikasi Anda tetap sama jika pengguna untuk sementara beralih ke aplikasi lain, lalu kembali ke aplikasi Anda nanti. Misalnya, pengguna melakukan penelusuran di layar, lalu menekan tombol layar utama atau menjawab panggilan telepon - saat kembali ke layar penelusuran, pengguna berharap menemukan kata kunci penelusuran dan hasilnya tetap ada di sana, persis seperti sebelumnya.

Dalam skenario ini, aplikasi Anda ditempatkan di latar belakang, dan sistem melakukan yang terbaik untuk mempertahankan proses aplikasi Anda dalam memori. Akan tetapi, sistem dapat menghancurkan proses aplikasi saat pengguna berinteraksi dengan aplikasi lain. Dalam kasus tersebut, aktivitas host dihancurkan, beserta status apa saja yang tersimpan di dalamnya. Saat pengguna meluncurkan ulang aplikasi, layar secara tidak terduga berada dalam status bersih. Untuk mempelajari penghentian proses lebih lanjut, lihat Proses dan siklus proses aplikasi.

Opsi untuk mempertahankan status UI

Jika ekspektasi pengguna tentang status UI tidak sesuai dengan perilaku sistem default, Anda harus menyimpan dan memulihkan status UI pengguna untuk memastikan bahwa penghancuran yang dimulai oleh sistem bersifat transparan kepada pengguna.

Tiap opsi untuk mempertahankan status UI bervariasi di sepanjang dimensi berikut yang memengaruhi pengalaman pengguna:

ViewModel Status tersimpan Penyimpanan persisten
Lokasi penyimpanan dalam memori dalam memori pada disk atau jaringan
Tetap bertahan saat terjadi perubahan konfigurasi Ya Ya Ya
Tetap bertahan saat terjadi penghentian proses yang dimulai oleh sistem Tidak Ya Ya
Tetap bertahan saat terjadi penutupan layar menyeluruh pengguna/finish() Tidak Tidak Ya
Keterbatasan data objek kompleks tidak masalah, tetapi ruang dibatasi oleh memori yang tersedia hanya untuk jenis primitif dan objek kecil sederhana seperti String hanya dibatasi oleh kapasitas disk atau biaya/waktu pengambilan dari resource jaringan
Waktu baca/tulis cepat (hanya akses memori) lambat (memerlukan serialisasi/deserialisasi) lambat (memerlukan akses disk atau transaksi jaringan)

Menggunakan ViewModel untuk menangani perubahan konfigurasi

ViewModel ideal untuk menyimpan dan mengelola data terkait UI saat pengguna aktif menggunakan aplikasi. Ini memungkinkan akses cepat ke data UI dan membantu Anda menghindari pengambilan kembali data dari jaringan atau disk selama rotasi, perubahan ukuran jendela, dan perubahan konfigurasi lainnya yang umum terjadi. Untuk mempelajari cara mengimplementasikan ViewModel, lihat panduan ViewModel.

ViewModel menyimpan data dalam memori, yang artinya lebih murah diambil daripada data dari disk atau jaringan. ViewModel dikaitkan dengan pemilik siklus proses, seperti tujuan Navigation atau aktivitas. ViewModel tetap berada dalam memori selama perubahan konfigurasi dan sistem secara otomatis mengaitkan ViewModel dengan instance pemilik siklus proses baru yang dihasilkan dari perubahan konfigurasi.

Tidak seperti status tersimpan, ViewModel dihancurkan selama penghentian proses yang dimulai oleh sistem. Untuk memuat ulang data setelah penghentian yang dimulai oleh sistem di ViewModel, gunakan SavedStateHandle API. Atau, jika data terkait dengan UI dan tidak perlu disimpan di ViewModel, gunakan rememberSerializable. Untuk jenis data primitif atau skenario saat Anda tidak ingin menggunakan @Serializable, gunakan rememberSaveable. Jika datanya adalah data aplikasi, mungkin lebih baik pertahankan data ke disk.

Jika Anda telah menyiapkan solusi dalam memori untuk menyimpan status UI selama perubahan konfigurasi, Anda mungkin tidak perlu menggunakan ViewModel.

Menggunakan status tersimpan sebagai cadangan untuk menangani penghentian proses yang dimulai oleh sistem

API seperti rememberSerializable dan rememberSaveable di Compose serta SavedStateHandle di ViewModel menyimpan data yang diperlukan untuk memuat ulang status UI jika sistem menghancurkan dan membuat ulang komponen. Untuk menangani struktur data yang kompleks secara lebih efisien, SavedStateHandle mendukung Serialisasi Kotlinx melalui ekstensi saved {}, sehingga Anda dapat menyimpan dan memulihkan objek dengan nilai yang jelas secara lancar bersama dengan jenis primitif standar. Untuk mempelajari cara mengimplementasikan status tersimpan menggunakan rememberSaveable, lihat Status dan Jetpack Compose.

Paket status tersimpan tetap bertahan saat terjadi perubahan konfigurasi dan penghentian proses, tetapi dibatasi oleh penyimpanan dan kecepatan karena API yang berbeda melakukan serialisasi data. Serialisasi dapat menghabiskan banyak memori jika objek yang diserialisasi rumit. Karena proses ini terjadi pada thread utama selama perubahan konfigurasi, serialisasi yang berjalan lama dapat menyebabkan penurunan frame dan tampilan visual yang patah-patah.

Jangan gunakan status tersimpan untuk menyimpan data dalam jumlah besar, seperti bitmap, atau struktur data kompleks yang memerlukan serialisasi atau deserialisasi yang panjang. Simpan hanya jenis primitif dan objek yang kecil dan sederhana seperti String. Oleh karena itu, gunakan status tersimpan untuk menyimpan data dalam jumlah minimum yang diperlukan, seperti ID, untuk membuat ulang data yang diperlukan guna memulihkan UI kembali ke status sebelumnya jika mekanisme persistensi lainnya gagal. Sebagian besar aplikasi harus menerapkannya untuk menangani proses penghentian yang dimulai oleh sistem.

Bergantung pada kasus penggunaan aplikasi, Anda mungkin tidak perlu menggunakan status yang disimpan sama sekali. Misalnya, browser mungkin membawa pengguna kembali ke halaman web yang mereka lihat sebelum keluar dari browser. Jika aktivitas Anda menunjukkan perilaku seperti ini, Anda dapat mengabaikannya menggunakan status tersimpan dan mempertahankan semuanya secara lokal.

Selain itu, saat Anda membuka aktivitas dari suatu intent, paket yang berisikan tambahan dikirimkan ke aktivitas saat perubahan konfigurasi dan saat sistem memulihkan aktivitas. Jika sepotong data status UI, seperti kueri penelusuran, diteruskan sebagai tambahan intent saat aktivitas diluncurkan, Anda dapat menggunakan paket tambahan, bukan paket status tersimpan. Untuk mempelajari tambahan intent lebih lanjut, lihat Intent dan Filter Intent.

Dalam salah satu skenario ini, Anda tetap harus menggunakan ViewModel untuk menghindari pemborosan siklus pemuatan ulang data dari database selama perubahan konfigurasi.

Jika ingin mempertahankan data UI yang sederhana dan ringan, Anda dapat menggunakan API status tersimpan saja untuk mempertahankan data status.

Mengaitkan ke status tersimpan menggunakan SavedStateRegistry

Mulai dari Fragment 1.1.0 atau Activity 1.0.0 dependensi transitifnya, komponen UI, seperti ComponentActivity, mengimplementasikan SavedStateRegistryOwner dan memberikan SavedStateRegistry yang terikat dengan komponen tersebut. SavedStateRegistry memungkinkan komponen agar terkait ke status tersimpan untuk memakainya atau berkontribusi untuknya. Misalnya, modul Status Tersimpan untuk ViewModel menggunakan SavedStateRegistry untuk membuat SavedStateHandle dan memberikannya ke objek ViewModel Anda. Anda dapat mengambil SavedStateRegistry dari dalam pemilik siklus proses dengan memanggil savedStateRegistry.

Komponen yang berkontribusi pada status tersimpan harus mengimplementasikan SavedStateRegistry.SavedStateProvider, yang menentukan satu metode yang disebut saveState(). Metode saveState() memungkinkan komponen Anda menampilkan Bundle yang berisi status apa pun yang harus disimpan dari komponen tersebut. SavedStateRegistry memanggil metode ini selama fase status penyimpanan siklus proses pemilik siklus proses.

  class SearchManager : SavedStateRegistry.SavedStateProvider {
      companion object {
          private const val QUERY = "query"
      }

      private val query: String? = null

      ...

      override fun saveState(): Bundle {
          return bundleOf(QUERY to query)
      }
  }

Untuk mendaftarkan SavedStateProvider, panggil registerSavedStateProvider() di SavedStateRegistry, dengan meneruskan kunci untuk dikaitkan dengan data penyedia serta penyedia. Data yang disimpan sebelumnya untuk penyedia dapat diambil dari status yang disimpan dengan memanggil consumeRestoredStateForKey() pada SavedStateRegistry, yang meneruskan kunci yang terkait dengan data penyedia.

Dalam ComponentActivity, Anda dapat mendaftarkan SavedStateProvider dalam onCreate() setelah memanggil super.onCreate(). Atau, Anda dapat menyetel LifecycleObserver di SavedStateRegistryOwner, yang mengimplementasikan LifecycleOwner, dan mendaftarkan SavedStateProvider setelah peristiwa ON_CREATE terjadi. Dengan menggunakan LifecycleObserver, Anda dapat memisahkan pendaftaran dan pengambilan status tersimpan sebelumnya dari SavedStateRegistryOwner itu sendiri.

  class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
      companion object {
          private const val PROVIDER = "search_manager"
          private const val QUERY = "query"
      }

      private val query: String? = null

      init {
          // Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
          registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
              if (event == Lifecycle.Event.ON_CREATE) {
                  val registry = registryOwner.savedStateRegistry

                  // Register this object for future calls to saveState()
                  registry.registerSavedStateProvider(PROVIDER, this)

                  // Get the previously saved state and restore it
                  val state = registry.consumeRestoredStateForKey(PROVIDER)

                  // Apply the previously saved state
                  query = state?.getString(QUERY)
              }
          }
      }

      override fun saveState(): Bundle {
          return bundleOf(QUERY to query)
      }

      ...
  }

  class SearchActivity : ComponentActivity() {
    private var searchManager = SearchManager(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Set up your Compose UI here
        setContent {
            // ...
        }
    }
  }

Menggunakan persistensi lokal untuk menangani penghentian proses untuk data yang kompleks atau besar

Penyimpanan lokal persisten, seperti database atau DataStore, akan bertahan selama aplikasi Anda diinstal di perangkat pengguna (kecuali pengguna menghapus data untuk aplikasi). Meskipun penyimpanan lokal tersebut bertahan selama kematian proses aplikasi yang diprakarsai oleh sistem, penyimpanan lokal tersebut dapat menjadi mahal untuk diambil karena harus dibaca dari penyimpanan lokal ke dalam memori. Biasanya penyimpanan lokal persisten ini mungkin sudah menjadi bagian dari arsitektur aplikasi untuk menyimpan semua data yang ingin Anda pertahankan jika Anda membuka dan menutup aplikasi.

ViewModel atau status yang disimpan menggunakan rememberSerializable, rememberSaveable, atau SavedStateHandle bukan solusi penyimpanan jangka panjang dan oleh karena itu bukan pengganti penyimpanan lokal, seperti database. Anda harus menggunakan mekanisme ini hanya untuk menyimpan status UI sementara dan menggunakan penyimpanan persisten untuk data aplikasi lainnya. Lihat Panduan Arsitektur Aplikasi untuk mengetahui detail selengkapnya tentang cara memanfaatkan penyimpanan lokal untuk mempertahankan data model aplikasi dalam jangka panjang (misalnya, setelah perangkat dimulai ulang beberapa kali).

Mengelola status UI: membagi dan menyelesaikan tugas

Anda dapat menyimpan dan memulihkan status UI secara efisien dengan cara membagi tugas ke beberapa jenis mekanisme persistensi. Biasanya, setiap mekanisme ini harus menyimpan jenis data yang berbeda yang digunakan dalam aplikasi, berdasarkan keseimbangan kompleksitas data, kecepatan akses, dan masa pakai:

  • Persistensi lokal: Menyimpan semua data aplikasi yang ingin Anda pertahankan jika Anda membuka dan menutup aplikasi.
    • Contoh: Kumpulan objek lagu, yang dapat mencakup file audio dan metadata.
  • ViewModel: Menyimpan semua data yang diperlukan untuk menampilkan UI terkait, status UI layar di memori.
    • Contoh: Objek lagu dari penelusuran terbaru dan kueri penelusuran terbaru.
  • Status tersimpan (rememberSerializable, rememberSaveable, dan SavedStateHandle): Menyimpan sejumlah kecil data yang diperlukan untuk memuat ulang status UI jika sistem berhenti lalu membuat ulang UI. Daripada menyimpan objek kompleks di sini, pertahankan objek kompleks pada penyimpanan lokal dan simpan ID unik objek tersebut dalam API status yang disimpan.
    • Contoh: Menyimpan kueri penelusuran terbaru.

Sebagai contoh, pertimbangkan aplikasi yang memungkinkan Anda menelusuri koleksi lagu Anda. Berikut adalah cara menangani berbagai peristiwa yang berbeda:

Saat pengguna menambahkan lagu, ViewModel segera mendelegasikan untuk mempertahankan data ini secara lokal. Jika lagu yang baru ditambahkan ini akan ditampilkan di UI, Anda juga harus memperbarui data dalam objek ViewModel untuk mencerminkan penambahan lagu. Ingatlah untuk melakukan semua penyisipan database dari thread utama.

Saat pengguna menelusuri lagu, data lagu kompleks apa pun yang Anda muat dari database akan segera disimpan dalam objek ViewModel sebagai bagian dari status UI layar.

Saat aplikasi beralih ke latar belakang dan sistem menyimpan status, kueri penelusuran harus disimpan menggunakan API status tersimpan, jika proses dibuat ulang. Karena informasi diperlukan untuk memuat data aplikasi yang dipertahankan dalam hal ini, simpan kueri penelusuran di SavedStateHandle ViewModel, atau gunakan rememberSerializable atau rememberSaveable di composable Anda. Ini adalah semua informasi yang Anda perlukan untuk memuat data dan mengembalikan UI ke statusnya saat ini.

Memulihkan status kompleks: menyusun ulang setiap bagian

Jika saatnya bagi pengguna untuk kembali ke aplikasi, ada dua kemungkinan skenario dalam membuat ulang UI:

  • UI dibuat ulang setelah sistem mengakhiri proses aplikasi. Sistem memiliki kueri yang tersimpan menggunakan API status tersimpan. ViewModel (menggunakan SavedStateHandle) atau composable (menggunakan rememberSerializable atau rememberSaveable) akan otomatis memulihkan kueri. Jika composable memulihkan kueri, composable akan meneruskan kueri ke ViewModel. ViewModel mengetahui bahwa hasil penelusuran belum disimpan ke cache dan mendelegasikan pemuatan hasil penelusuran menggunakan kueri penelusuran yang ditentukan.
  • UI dibuat ulang setelah perubahan konfigurasi. Karena instance ViewModel belum dihancurkan, ViewModel menyimpan semua informasi dalam cache ke dalam memori dan tidak perlu mengkueri ulang database.

Referensi lainnya

Untuk mempelajari cara menyimpan status UI lebih lanjut, lihat referensi berikut.

Codelab

Melihat konten