Loader

Loader tidak digunakan lagi di Android 9 (API level 28). Opsi yang direkomendasikan untuk menangani pemuatan data saat menangani siklus proses Activity dan Fragment adalah menggunakan kombinasi objek ViewModel dan LiveData. Model tampilan tidak terpengaruh oleh perubahan konfigurasi, seperti loader, tetapi tanpa kode boilerplate. LiveData menyediakan cara berbasis siklus proses untuk memuat data yang dapat Anda gunakan kembali dalam berbagai model tampilan. Anda juga dapat menggabungkan LiveData menggunakan MediatorLiveData. Setiap kueri yang dapat diamati, seperti yang berasal dari Database Room, dapat digunakan untuk mengamati perubahan pada data.

ViewModel dan LiveData juga tersedia dalam situasi saat Anda tidak memiliki akses ke LoaderManager, seperti dalam Service. Menggunakan keduanya secara bersamaan memberikan cara mudah untuk mengakses data yang dibutuhkan aplikasi Anda tanpa harus berurusan dengan siklus proses UI. Untuk mempelajari LiveData lebih lanjut, lihat ringkasan LiveData. Untuk mempelajari ViewModel lebih lanjut, 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:

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

Loader memecahkan 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 bila terjadi peristiwa.
  • Loader mempertahankan dan meng-cache hasil saat konfigurasi berubah untuk mencegah kueri duplikat.
  • Loader dapat mengimplementasikan pengamat untuk memantau perubahan dalam sumber data pokok. Misalnya, CursorLoader akan otomatis mendaftarkan ContentObserver untuk memicu pemuatan ulang saat data berubah.

Ringkasan Loader API

Ada beberapa class dan antarmuka yang mungkin terlibat saat menggunakan loader di aplikasi. Semuanya diringkas dalam tabel berikut:

Class/Antarmuka Deskripsi
LoaderManager Class abstrak yang terkait dengan FragmentActivity atau Fragment untuk mengelola satu atau beberapa instance Loader. 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 initLoader() atau restartLoader(). Sistem secara otomatis menentukan apakah loader dengan ID bilangan bulat yang sama sudah ada, dan akan membuat loader baru atau menggunakan kembali loader yang ada.

LoaderManager.LoaderCallbacks Antarmuka ini berisi metode callback yang dipanggil saat peristiwa loader terjadi. Antarmuka menetapkan tiga metode callback:
  • onCreateLoader(int, Bundle): dipanggil saat sistem memerlukan loader baru untuk dibuat. Dalam kode Anda, buat objek Loader dan kembalikan ke sistem.
  • onLoadFinished(Loader<D>, D): dipanggil saat loader selesai memuat data. Anda biasanya menampilkan data kepada pengguna dalam kode Anda.
  • onLoaderReset(Loader<D>): dipanggil saat loader yang dibuat sebelumnya sedang direset, saat Anda memanggil destroyLoader(int), atau saat aktivitas atau fragmen dihancurkan, sehingga datanya tidak tersedia. Di kode Anda, hapus semua referensi ke data loader.
Aktivitas atau fragmen Anda biasanya mengimplementasikan antarmuka ini, dan terdaftar saat Anda memanggil initLoader() atau restartLoader().
Loader Loader melakukan pemuatan data. Class ini bersifat abstrak dan berfungsi sebagai class dasar untuk semua loader. Anda dapat langsung membuat subclass Loader atau menggunakan salah satu subclass bawaan berikut untuk menyederhanakan implementasi:

Bagian berikut menunjukkan 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 hal-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 melakukan inisialisasi Loader dalam metode onCreate() aktivitas atau metode onCreate() fragmen. Anda melakukannya dengan cara 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 yang akan disediakan ke loader saat pembuatan (null dalam contoh ini).
  • Implementasi LoaderManager.LoaderCallbacks, yang dipanggil oleh LoaderManager untuk melaporkan peristiwa loader. Dalam contoh ini, class lokal mengimplementasikan antarmuka LoaderManager.LoaderCallbacks sehingga meneruskan referensi ke dirinya sendiri, this.

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

  • Jika loader yang ditentukan oleh ID sudah ada, loader yang terakhir dibuat akan digunakan kembali.
  • Jika loader yang ditentukan oleh ID tidak ada, initLoader() akan 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, implementasi LoaderManager.LoaderCallbacks yang diberikan akan dikaitkan dengan loader dan dipanggil saat status loader berubah. Jika, pada saat panggilan ini, pemanggil dalam status dimulai dan loader yang diminta sudah ada dan telah menghasilkan datanya, sistem akan segera memanggil onLoadFinished() selama initLoader(). Anda harus siap untuk hal ini terjadi. Untuk pembahasan selengkapnya tentang callback ini, lihat bagian tentang onLoadFinished.

Metode initLoader() menampilkan Loader yang dibuat, tetapi Anda tidak perlu mengambil referensi ke metode tersebut. LoaderManager akan mengelola masa aktif loader secara otomatis. LoaderManager memulai dan menghentikan pemuatan jika diperlukan dan mempertahankan status loader serta konten yang terkait.

Artinya, Anda jarang berinteraksi dengan loader secara langsung. Anda paling sering menggunakan metode LoaderManager.LoaderCallbacks untuk mengintervensi proses pemuatan saat peristiwa tertentu terjadi. Untuk diskusi selengkapnya mengenai topik ini, lihat bagian Menggunakan callback AndroidManifest.

Memulai ulang loader

Ketika Anda menggunakan initLoader(), seperti yang ditunjukkan di bagian sebelumnya, metode ini akan menggunakan loader yang sudah ada dengan ID yang ditentukan, jika ada. Jika tidak ada, sistem akan membuatnya. Terkadang Anda perlu membuang data lama dan memulai dari awal.

Untuk menghapus data lama, gunakan restartLoader(). Misalnya, implementasi SearchView.OnQueryTextListener berikut akan memulai ulang loader saat kueri pengguna berubah. Loader perlu dimulai ulang agar dapat menggunakan filter penelusuran yang 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 AndroidManifest

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

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

Anda menggunakan metode LoaderManager.LoaderCallbacks untuk mengetahui kapan harus membuat loader baru dan memberi tahu aplikasi kapan waktunya berhenti menggunakan data loader.

LoaderManager.LoaderCallbacks menyertakan metode berikut:

  • onLoadFinished(): dipanggil saat loader yang dibuat sebelumnya selesai dimuat.
  • onLoaderReset(): dipanggil saat loader yang dibuat sebelumnya sedang direset, sehingga datanya tidak tersedia.

Metode ini dijelaskan lebih detail dalam bagian berikutnya.

onCreateLoader

Saat Anda mencoba mengakses loader, misalnya melalui initLoader(), akan ada pemeriksaan untuk mengetahui apakah loader yang ditetapkan oleh ID sudah ada. Jika tidak, metode LoaderManager.LoaderCallbacks akan terpicu, onCreateLoader(). Di sinilah Anda membuat loader baru. Biasanya ini adalah CursorLoader, tetapi Anda dapat menerapkan subclass Loader Anda sendiri.

Pada contoh berikut, metode callback onCreateLoader() membuat CursorLoader menggunakan metode konstruktornya, yang memerlukan kumpulan informasi lengkap yang diperlukan untuk melakukan kueri ke ContentProvider. Secara khusus, pengujian memerlukan hal berikut:

  • uri: URI untuk konten yang akan diambil.
  • projection: daftar kolom yang akan ditampilkan. Meneruskan null akan menampilkan semua kolom, dan ini tidak efisien.
  • selection: filter yang mendeklarasikan baris yang akan ditampilkan, yang diformat sebagai klausa SQL WHERE (tidak termasuk WHERE itu sendiri). Meneruskan null akan menampilkan semua baris untuk URI yang diberikan.
  • selectionArgs: jika Anda menyertakan ?s dalam pilihan, nilai tersebut akan diganti dengan nilai dari selectionArgs sesuai urutan kemunculannya dalam pilihan. Nilai-nilai terikat sebagai string.
  • sortOrder: cara mengurutkan baris, dengan format klausa ORDER BY dari SQL (tidak termasuk ORDER BY itu sendiri). Meneruskan 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 akan dirilis. Namun, jangan merilis data sendiri karena loader memilikinya dan akan menanganinya.

Loader akan melepas data setelah mengetahui bahwa aplikasi tidak lagi menggunakannya. Misalnya, jika data adalah kursor dari CursorLoader, jangan panggil close() di dalamnya. Jika kursor ditempatkan di CursorAdapter, gunakan metode swapCursor() agar 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 ketika loader yang dibuat sebelumnya sedang direset, sehingga datanya tidak tersedia. Callback ini memungkinkan Anda mengetahui kapan data akan dirilis sehingga Anda dapat menghapus referensi ke callback tersebut.

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 Fragment yang menampilkan ListView yang berisi hasil kueri terhadap penyedia konten kontak. CursorLoader digunakan untuk mengelola kueri pada penyedia.

Karena contoh ini berasal dari aplikasi untuk mengakses kontak pengguna, manifesnya harus menyertakan READ_CONTACTS izin.

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: