Loader

Loader tidak digunakan lagi mulai Android 9 (API level 28). Opsi yang direkomendasikan untuk menangani pemuatan data saat menangani siklus proses Activity dan Fragment adalah dengan menggunakan kombinasi objek ViewModel dan LiveData. Model tampilan tetap bertahan saat terjadi perubahan konfigurasi, seperti loader, tetapi dengan lebih sedikit kode boilerplate. LiveData menyediakan cara yang mendukung siklus proses untuk memuat data yang dapat Anda gunakan kembali multi-tampilan. Anda juga dapat menggabungkan LiveData menggunakan MediatorLiveData. Kueri apa pun yang dapat diamati, seperti kueri dari Database Room, dapat digunakan untuk mengamati perubahan pada data.

ViewModel dan LiveData juga tersedia dalam situasi ketika Anda tidak memiliki akses ke LoaderManager, seperti di file Service. Menggunakan keduanya dalam tandem menyediakan cara mudah untuk mengakses data yang dibutuhkan aplikasi Anda tanpa harus berurusan dengan UI siklus proses. Untuk mempelajari LiveData lebih lanjut, lihat Ringkasan LiveData. Untuk mempelajari lebih lanjut tentang ViewModel, lihat ringkasan ViewModel.

Loader API memungkinkan Anda memuat data dari penyedia konten atau sumber data lainnya untuk ditampilkan di FragmentActivity atau Fragment.

Tanpa loader, beberapa masalah yang mungkin Anda temui meliputi hal berikut:

  • Jika Anda mengambil data secara langsung dalam aktivitas atau fragmen, pengguna Anda mengalami kurangnya responsivitas karena kinerja yang berpotensi lambat kueri dari thread UI.
  • Jika Anda mengambil data dari thread lain, mungkin dengan AsyncTask, maka Anda bertanggung jawab untuk mengelola thread itu dan UI thread melalui berbagai peristiwa siklus proses aktivitas atau fragmen, seperti onDestroy() dan perubahan konfigurasi.

Loader mengatasi masalah ini dan menyertakan manfaat lainnya:

  • Loader berjalan di thread terpisah untuk mencegah UI yang lambat atau tidak responsif.
  • Loader menyederhanakan pengelolaan thread dengan menyediakan metode callback saat peristiwa terjadi.
  • Loader mempertahankan dan meng-cache hasil ketika terjadi perubahan konfigurasi untuk mencegah duplikasi kueri.
  • Loader bisa mengimplementasikan pengamat untuk memantau perubahan di sumber data. Misalnya, CursorLoader akan otomatis mendaftarkan ContentObserver untuk memicu pemuatan ulang saat data berubah.

Ringkasan Loader API

Ada beberapa class dan antarmuka yang mungkin dilibatkan saat menggunakan loader di dalam aplikasi. Keduanya dirangkum dalam tabel berikut:

Class/Antarmuka Deskripsi
LoaderManager Class abstrak yang terkait dengan FragmentActivity atau Fragment untuk mengelola satu atau beberapa Loader instance. Hanya ada satu LoaderManager per aktivitas atau fragmen, tetapi LoaderManager dapat mengelola beberapa loader.

Untuk mendapatkan LoaderManager, panggil getSupportLoaderManager() dari aktivitas atau fragmen.

Untuk mulai memuat data dari loader, panggil salah satu initLoader() atau restartLoader(). Sistem secara otomatis akan menentukan apakah loader dengan ID integer yang sama ada dan membuat loader baru atau menggunakan kembali loader yang sudah ada.

LoaderManager.LoaderCallbacks Antarmuka ini berisi metode callback yang dipanggil saat terjadi peristiwa loader. Antarmuka menetapkan tiga metode callback:
  • onCreateLoader(int, Bundle): dipanggil saat sistem membutuhkan loader baru untuk dibuat. Di kode Anda, membuat objek Loader dan mengembalikannya ke sistem.
  • onLoadFinished(Loader<D>, D): dipanggil ketika loader telah selesai memuat data. Anda biasanya menampilkan data kepada pengguna dalam kode Anda.
  • onLoaderReset(Loader<D>): akan dipanggil bila loader yang dibuat sebelumnya sedang di-reset, saat Anda memanggil destroyLoader(int) atau saat aktivitas atau fragmen dihancurkan, sehingga datanya tidak tersedia. Di kode Anda, menghapus semua referensi ke data loader.
Aktivitas atau fragmen Anda biasanya mengimplementasikan antarmuka ini, dan terdaftar saat Anda menelepon initLoader() atau restartLoader().
Loader Loader melakukan pemuatan data. Class ini bersifat abstrak dan berfungsi sebagai kelas dasar untuk semua loader. Anda dapat langsung membuat subclass Loader atau gunakan salah satu fitur bawaan berikut subclass untuk menyederhanakan implementasi:

Bagian berikut menunjukkan cara menggunakannya dan antarmuka dalam aplikasi.

Menggunakan loader dalam aplikasi

Bagian ini menjelaskan cara menggunakan loader dalam aplikasi Android. Channel aplikasi yang menggunakan loader biasanya berisi hal berikut:

Memulai loader

LoaderManager mengelola satu atau beberapa instance Loader dalam FragmentActivity atau Fragment. Hanya ada satu LoaderManager per aktivitas atau fragmen.

Anda biasanya lakukan inisialisasi Loader dalam metode onCreate() aktivitas atau dalam metode Metode onCreate(). Anda lakukan seperti berikut:

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 di konstruksi (null dalam contoh ini).
  • Implementasi LoaderManager.LoaderCallbacks, yang panggilan LoaderManager untuk melaporkan peristiwa loader. Di sini misalnya, class lokal mengimplementasikan antarmuka LoaderManager.LoaderCallbacks sehingga meneruskan referensi menjadi dirinya sendiri, this.

Panggilan initLoader() memastikan bahwa loader diinisialisasi dan aktif. Ia memiliki dua kemungkinan hasil:

  • Jika loader yang disebutkan oleh ID sudah ada, loader yang terakhir dibuat akan digunakan kembali.
  • Jika loader yang ditentukan oleh ID tidak ada, initLoader() memicu Metode LoaderManager.LoaderCallbacks onCreateLoader(). Di sinilah Anda mengimplementasikan kode untuk membuat instance dan mengembalikan loader baru. Untuk diskusi selengkapnya, lihat bagian tentang onCreateLoader.

Dalam kedua kasus tersebut, LoaderManager.LoaderCallbacks yang diberikan dikaitkan dengan loader dan dipanggil ketika perubahan status loader. Jika, pada saat panggilan ini, pemanggil berada dalam keadaan dimulai dan loader yang diminta sudah ada serta telah membuat data, sistem akan memanggil onLoadFinished() selama initLoader(). Anda harus siap untuk hal ini. Untuk diskusi selengkapnya tentang callback ini, lihat bagian tentang onLoadFinished.

Metode initLoader() akan menampilkan Loader yang dibuat, tetapi Anda tidak perlu menangkap referensi ke sana. LoaderManager mengelola masa pakai loader secara otomatis. LoaderManager memulai dan menghentikan pemuatan bila perlu dan menjaga status loader dan konten yang terkait.

Seperti yang tersirat di sini, Anda jarang berinteraksi dengan loader secara langsung. Anda paling sering menggunakan metode LoaderManager.LoaderCallbacks untuk melakukan intervensi dalam pemuatan proses saat peristiwa tertentu terjadi. Untuk diskusi selengkapnya mengenai topik ini, lihat bagian Menggunakan callback Interface.

Memulai ulang loader

Saat Anda menggunakan initLoader(), sebagai ditunjukkan di bagian sebelumnya, loader yang sudah ada menggunakan ID yang ditetapkan jika ada. Jika tidak ada, Anda akan membuatnya. Namun terkadang Anda ingin membuang data lama Anda dan memulai dari awal.

Untuk menghapus data lama, gunakan restartLoader(). Misalnya, implementasi SearchView.OnQueryTextListener mulai ulang loader saat kueri pengguna berubah. Loader perlu dimulai ulang bahwa filter tersebut 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 Interface

LoaderManager.LoaderCallbacks adalah antarmuka callback yang memungkinkan klien berinteraksi dengan LoaderManager.

Loader, khususnya CursorLoader, diharapkan mempertahankan data mereka setelah dihentikan. Hal ini memungkinkan aplikasi menjaga data di seluruh metode onStop() dan onStart() aktivitas atau fragmen, sehingga saat pengguna kembali ke aplikasi, mereka tidak perlu menunggu data memuat ulang.

Anda menggunakan metode LoaderManager.LoaderCallbacks untuk mengetahui waktu membuat loader baru dan memberi tahu aplikasi kapan loader perlu dibuat waktu untuk berhenti menggunakan data loader.

LoaderManager.LoaderCallbacks menyertakannya metode:

  • onLoadFinished(): dipanggil bila loader yang dibuat sebelumnya telah menyelesaikan pemuatannya.
  • onLoaderReset(): dipanggil bila loader yang dibuat sebelumnya sedang di-reset, sehingga membuat data tidak tersedia.

Metode ini dijelaskan lebih detail dalam bagian berikutnya.

onCreateLoader

Saat Anda mencoba mengakses loader, seperti melalui initLoader(), loader ini akan memeriksa apakah loader yang ditetapkan oleh ID ada. Jika tidak, metode LoaderManager.LoaderCallbacks akan dipicu onCreateLoader(). Ini adalah tempat Anda membuat loader baru. Biasanya ini adalah CursorLoader, tetapi Anda dapat mengimplementasikan subclass Loader Anda sendiri.

Dalam contoh berikut, onCreateLoader() membuat CursorLoader menggunakan metode konstruktornya, yang memerlukan set informasi lengkap yang diperlukan untuk melakukan kueri ke ContentProvider. Khususnya, diperlukan hal berikut:

  • uri: URI untuk konten yang akan diambil.
  • projection: daftar kolom yang akan ditampilkan. Lulus null menampilkan semua kolom, sehingga tidak efisien.
  • selection: filter yang mendeklarasikan baris mana yang akan ditampilkan, diformat sebagai klausa SQL WHERE (tidak termasuk WHERE itu sendiri). Lulus null menampilkan semua baris untuk URI yang ditentukan.
  • selectionArgs: jika Anda menyertakan ?s dalam pilihan, baris tersebut diganti dengan nilai dari selectionArgs sesuai dengan urutan kemunculannya yang dipilih. Nilai-nilai diikat sebagai string.
  • sortOrder: cara mengurutkan baris, yang diformat sebagai SQL Klausa ORDER BY (tidak termasuk ORDER BY itu sendiri). Melewati null menggunakan tata urutan default, yang mungkin tidak berurutan.

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 menyelesaikan pemuatannya. Metode ini dijamin akan dipanggil sebelum perilisan data terakhir yang disediakan untuk loader ini. Pada tahap ini, hapus semua penggunaan data lama, karena data itu akan dirilis. Tapi jangan lepaskan data diri Anda sendiri—loader memilikinya dan menanganinya.

Loader melepaskan data setelah mengetahui bahwa aplikasi tidak lagi menggunakannya. Misalnya, jika data adalah kursor dari CursorLoader, jangan memanggil close() sendiri. Jika kursor ditempatkan di CursorAdapter, gunakan metode swapCursor() sehingga Cursor lama tidak ditutup, seperti yang ditunjukkan dalam contoh berikut:

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 direset, sehingga membuat datanya tidak tersedia. Callback ini memungkinkan Anda mengetahui kapan data tersebut akan dirilis sehingga Anda dapat menghapus referensinya.

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, berikut adalah implementasi lengkap dari Fragment yang menampilkan ListView yang berisi hasil kueri terhadap penyedia konten kontak. Fungsi ini menggunakan CursorLoader untuk mengelola kueri pada penyedia.

Karena contoh ini berasal dari aplikasi untuk mengakses kontak pengguna, manifes 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 onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        loaderManager.initLoader(0, null, this)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, 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
    }

    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 onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, 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);
    }

    @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 lainnya

Contoh berikut ini mengilustrasikan cara menggunakan loader: