Loader tidak digunakan lagi di Android P (API 28). Opsi yang disarankan untuk menangani pemuatan data saat menangani siklus hidup Aktivitas dan Fragmen adalah menggunakan kombinasi ViewModels
dan LiveData
. ViewModels bertahan dari perubahan konfigurasi seperti Loader tetapi dengan lebih sedikit boilerplate. LiveData memberikan cara lifecycle-aware pemuatan data yang bisa Anda gunakan kembali dalam beberapa ViewModels. Anda juga bisa menggabungkan LiveData menggunakan MediatorLiveData
, dan kueri yang dapat diamati, seperti kueri dari Room database, bisa digunakan untuk mengamati perubahan pada data. ViewModels dan LiveData juga tersedia pada situasi saat Anda tidak memiliki akses ke LoaderManager
, seperti dalam Service
. Menggunakan ViewModels dan liveData secara bersamaan memberikan cara mudah untuk mengakses data yang diperlukan aplikasi Anda tanpa harus berurusan dengan siklus hidup UI. Untuk mempelajari selengkapnya tentang LiveData lihat panduan LiveData dan untuk mempelajari selengkapnya tentang ViewModels lihat panduan ViewModel.
Loader API memungkinkan Anda memuat data dari penyedia konten atau dari sumber data lain untuk tampilan dalam FragmentActivity
atau Fragment
. Jika Anda tidak memahami mengapa Anda memerlukan Loader API untuk melakukan operasi yang tampaknya sepele, maka pertama-tama pertimbangkan beberapa masalah yang dapat Anda alami tanpa loader:
- Jika Anda mengambil data secara langsung dalam aktivitas atau fragmen, pengguna Anda akan kesulitan karena kurangnya responsif, karena melakukan kueri yang berpotensi lambat dari thread UI.
- Jika Anda mengambil data dari thread lain, mungkin dengan
AsyncTask
, maka Anda bertanggung jawab untuk mengelola thread dan thread UI melalui berbagai kejadian siklus hidup aktivitas atau fragmen, sepertionDestroy()
dan perubahan konfigurasi.
Loader memecahkan semua masalah ini dan menyertakan manfaat lain. Sebagai contoh:
- Loader berjalan di thread terpisah untuk mencegah UI tidak responsif atau tersendat.
- Loader menyederhanakan pengelolaan thread dengan menyediakan metode callback bila terjadi peristiwa.
- Loader mempertahankan dan membuat cache hasil ketika konfigurasi berubah untuk mencegah duplikasi kueri.
- Loader bisa mengimplementasikan pengamat untuk memantau perubahan dalam sumber data dasarnya. Misalnya,
CursorLoader
secara otomatis mendaftarkanContentObserver
untuk memicu muat ulang saat data berubah.
Rangkuman Loader API
Ada beberapa kelas dan antarmuka yang mungkin dilibatkan dalam menggunakan loader pada aplikasi. Semuanya dirangkum dalam tabel ini:
Kelas/Antarmuka | Keterangan |
---|---|
LoaderManager |
Kelas abstrak yang dikaitkan dengan FragmentActivity atau Fragment untuk mengelola satu atau beberapa instance Loader . Hanya ada satu LoaderManager per aktivitas atau fragmen, tetapi LoaderManager bisa mengelola beberapa loader.
Untuk mendapatkan LoaderManager, panggil Untuk memulai pemuatan data dari loader, panggil |
LoaderManager.LoaderCallbacks |
Antarmuka ini berisi metode callback yang dipanggil saat terjadi peristiwa loader. Antarmuka menetapkan tiga metode callback:
initLoader() atau restartLoader() .
|
Loader |
Loader melakukan pemuatan data. Kelas ini abstrak dan berfungsi sebagai kelas dasar untuk semua loader. Anda bisa secara langsung menjadikan subkelas Loader atau menggunakan salah satu subkelas bawaan berikut untuk memudahkan implementasi:
|
Bagian berikut ini menunjukkan kepada Anda cara menggunakan class dan antarmuka ini dalam aplikasi.
Menggunakan Loader dalam Aplikasi
Bagian ini menjelaskan cara menggunakan loader dalam aplikasi Android. Aplikasi yang menggunakan loader biasanya menyertakan yang berikut ini:
FragmentActivity
atauFragment
.- Instance
LoaderManager
. CursorLoader
untuk memuat data yang didukung olehContentProvider
. Atau, Anda dapat mengimplementasikan subkelas sendiri dariLoader
atauAsyncTaskLoader
untuk memuat data dari beberapa sumber lain.- Implementasi untuk
LoaderManager.LoaderCallbacks
. Inilah tempat membuat loader baru dan mengelola referensi Anda ke loader yang sudah ada. - Cara menampilkan data loader, seperti
SimpleCursorAdapter
. - Sumber data, seperti
ContentProvider
, saat menggunakanCursorLoader
.
Memulai Loader
LoaderManager
mengelola satu atau beberapa instance Loader
dalam FragmentActivity
atau Fragment
. Hanya ada satu LoaderManager
per aktivitas atau fragmen.
Anda biasanya melakukan inisialisasi Loader
dalam metode onCreate()
aktivitas, atau dalam metode onActivityCreated()
fragmen. Anda melakukannya dengan cara berikut ini:
Kotlin
supportLoaderManager.initLoader(0, null, this)
Java
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this);
Metode initLoader()
mengambil parameter berikut:
- ID unik yang mengidentifikasi loader. Dalam contoh ini, ID-nya adalah 0.
- Argumen opsional untuk dipasok ke loader pada saat pembuatan (dalam contoh ini
null
). - Implementasi
LoaderManager.LoaderCallbacks
, yang akan dipanggilLoaderManager
untuk melaporkan kejadian loader. Dalam contoh ini, kelas lokal mengimplementasikan antarmukaLoaderManager.LoaderCallbacks
, sehingga meneruskan referensi ke dirinya sendiri,this
.
Panggilan initLoader()
memastikan bahwa loader telah dimulai dan aktif. Ia memiliki dua kemungkinan hasil:
- Jika loader yang disebutkan oleh ID sudah ada, loader yang dibuat terakhir akan digunakan kembali.
- Jika loader yang ditentukan melalui ID tidak ada,
initLoader()
akan memicu metodeLoaderManager.LoaderCallbacks
metodeonCreateLoader()
. Di sinilah Anda mengimplementasikan kode untuk membuat instance dan mengembalikan loader baru. Untuk pembahasan selengkapnya, lihat bagian onCreateLoader.
Dalam hal ini, implementasi LoaderManager.LoaderCallbacks
yang diberikan akan dikaitkan dengan loader, dan akan dipanggil bila status loader berubah. Jika saat panggilan ini status pemanggil sudah dimulai, dan loader yang diminta sudah ada dan telah menghasilkan datanya, maka sistem segera memanggil onLoadFinished()
(selama initLoader()
), sehingga Anda harus siap bila hal ini terjadi. Lihat onLoadFinished untuk pembahasan selengkapnya mengenai callback ini
Perhatikan bahwa metode initLoader()
mengembalikan Loader
yang dibuat, namun Anda tidak perlu menangkap acuan ke sana. LoaderManager
mengelola masa hidup loader secara otomatis. LoaderManager
memulai dan menghentikan pemuatan jika perlu, dan menjaga status loader dan materi terkaitnya. Seperti yang tersirat di sini, Anda akan jarang berinteraksi dengan loader secara langsung (meskipun misalnya menggunakan metode loader untuk menyempurnakan perilaku loader, lihat contoh LoaderThrottle). Anda paling sering akan menggunakan metode LoaderManager.LoaderCallbacks
untuk mengintervensi proses pemuatan saat terjadi kejadian tertentu. Untuk diskusi selengkapnya mengenai topik ini, lihat Menggunakan Callback LoaderManager.
Memulai Ulang Loader
Bila Anda menggunakan initLoader()
, seperti ditampilkan di atas, loader yang ada akan digunakan dengan ID yang ditetapkan jika ada. Jika tidak ada, ID akan dibuat. Namun kadang-kadang Anda perlu membuang data lama dan mulai dari awal.
Untuk membuang data lama, gunakan restartLoader()
. Misalnya, implementasi SearchView.OnQueryTextListener
ini akan memulai ulang loader bila kueri pengguna berubah. Loader perlu dimulai ulang agar dapat menggunakan filter penelusuran yang telah direvisi untuk melakukan kueri baru:
Kotlin
fun onQueryTextChanged(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null supportLoaderManager.restartLoader(0, null, this) return true }
Java
public boolean onQueryTextChanged(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getSupportLoaderManager().restartLoader(0, null, this); return true; }
Menggunakan Callback LoaderManager
LoaderManager.LoaderCallbacks
adalah antarmuka callback yang memungkinkan klien berinteraksi dengan LoaderManager
.
Loader, khususnya CursorLoader
, diharapkan mempertahankan datanya setelah dihentikan. Ini memungkinkan aplikasi mempertahankan datanya di aktivitas atau metode onStop()
dan onStart()
fragmen, sehingga bila pengguna kembali ke aplikasi, mereka tidak harus menunggu data dimuat kembali. Anda menggunakan metode LoaderManager.LoaderCallbacks
untuk mengetahui waktu membuat loader baru, dan memberi tahu aplikasi kapan berhenti menggunakan data loader.
LoaderManager.LoaderCallbacks
berisi metode ini:
onCreateLoader()
— Membuat instance dan mengembalikanLoader
baru untuk ID yang diberikan.
-
onLoadFinished()
— Dipanggil bila loader yang dibuat sebelumnya selesai dimuat.
onLoaderReset()
— Dipanggil bila loader yang dibuat sebelumnya sedang disetel ulang, sehingga datanya tidak tersedia.
Metode ini dijelaskan lebih detail dalam bagian berikutnya.
onCreateLoader
Saat Anda mencoba mengakses loader (misalnya, melalui initLoader()
), ia akan memeriksa untuk mengetahui adanya loader yang ditetapkan melalui ID. Jika tidak ada, metode LoaderManager.LoaderCallbacks
onCreateLoader()
akan dipicu. Di sinilah Anda membuat loader baru. Biasanya ini berupa CursorLoader
, namun Anda bisa mengimplementasikan subkelas Loader
sendiri.
Dalam contoh ini, metode callback onCreateLoader()
membuat CursorLoader
. Anda harus membuat CursorLoader
menggunakan metode konstruktornya, yang memerlukan set informasi lengkap untuk melakukan kueri ke ContentProvider
. Khususnya, ia memerlukan:
- uri — URI untuk materi yang akan diambil.
- projection — Daftar berisi kolom yang akan dikembalikan. Meneruskan
null
akan mengembalikan semua kolom, jadi tidak efisien. - selection — Filter yang mendeklarasikan baris yang akan dikembalikan, diformat sebagai klausa SQL WHERE (tidak termasuk WHERE itu sendiri). Meneruskan
null
akan mengembalikan semua baris untuk URI yang diberikan. - selectionArgs — Anda dapat menyertakan ?s dalam pilihan, yang akan digantikan dengan nilai dari selectionArgs, agar muncul dalam pilihan. Nilai-nilai akan diikat sebagai String.
- sortOrder — Cara menyusun baris, diformat sebagai klausa ORDER BY dari SQL (tidak termasuk ORDER BY itu sendiri). Meneruskan
null
akan menggunakan urutan sortir default, yang mungkin tidak berurutan.
Sebagai contoh:
Kotlin
// If non-null, this is the current filter the user has provided. private var curFilter: String? = null ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { ContactsContract.Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") }
Java
// If non-null, this is the current filter the user has provided. String curFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
onLoadFinished
Metode ini dipanggil bila loader yang dibuat sebelumnya selesai dimuat. Metode ini dijamin dipanggil sebelum pelepasan data terakhir yang disediakan untuk loader ini. Di titik ini Anda harus membuang semua penggunaan data lama (karena akan segera dilepas), namun jangan melepas sendiri data tersebut karena loader memilikinya dan akan menanganinya.
Loader akan melepas data setelah mengetahui bahwa aplikasi tidak lagi menggunakannya. Misalnya, jika data adalah kursor dari CursorLoader
, Anda tidak boleh memanggil close()
pada dirinya sendiri. Jika kursor ditempatkan dalam CursorAdapter
, Anda harus menggunakan metode swapCursor()
agar Cursor
lama tidak ditutup. Sebagai contoh:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data); }
onLoaderReset
Metode ini dipanggil bila loader yang dibuat sebelumnya sedang disetel ulang, sehingga datanya tidak tersedia. Callback ini memungkinkan Anda mengetahui kapan data akan dilepas sehingga dapat membuang acuannya ke callback.
Implementasi ini memanggil swapCursor()
dengan nilai null
:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null); }
Contoh
Sebagai contoh, inilah implementasi penuh Fragment
yang menampilkan ListView
berisi hasil kueri terhadap penyedia materi kontak. CursorLoader
digunakan untuk mengelola kueri pada penyedia.
Agar aplikasi dapat mengakses kontak pengguna, seperti yang ditampilkan dalam contoh ini, manifesnya harus menyertakan izin READ_CONTACTS
.
Kotlin
private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf( Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY ) class CursorLoaderListFragment : ListFragment(), SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. private lateinit var mAdapter: SimpleCursorAdapter // If non-null, this is the current filter the user has provided. private var curFilter: String? = null override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText("No phone numbers") // We have a menu item to show in action bar. setHasOptionsMenu(true) // Create an empty adapter we will use to display the loaded data. mAdapter = SimpleCursorAdapter(activity, android.R.layout.simple_list_item_2, null, arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS), intArrayOf(android.R.id.text1, android.R.id.text2), 0 ) listAdapter = mAdapter // Prepare the loader. Either re-connect with an existing one, // or start a new one. loaderManager.initLoader(0, null, this) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { // Place an action bar item for searching. menu.add("Search").apply { setIcon(android.R.drawable.ic_menu_search) setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) actionView = SearchView(activity).apply { setOnQueryTextListener(this@CursorLoaderListFragment) } } } override fun onQueryTextChange(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null loaderManager.restartLoader(0, null, this) return true } override fun onQueryTextSubmit(query: String): Boolean { // Don't care about this. return true } override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: $id") } override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") } override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data) } override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null) } }
Java
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; // If non-null, this is the current filter the user has provided. String curFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText("No phone numbers"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } // These are the Contacts rows that we will retrieve. static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } }
Contoh Selengkapnya
Contoh berikut ini mengilustrasikan cara menggunakan loader:
- LoaderCursor — versi lengkap dari cuplikan yang ditampilkan di atas.
- Mendapatkan Kembali Daftar Kontak - panduan yang menggunakan
CursorLoader
mengambil kembali data dari penyedia kontak. - LoaderThrottle — contoh cara penggunaan throttling untuk mengurangi jumlah kueri yang dilakukan penyedia materi saat datanya berubah.
AsyncTaskLoader
- contoh yang menggunakanAsyncTaskLoader
untuk memuat aplikasi terinstal saat ini dari pengelola paket.