How to use CursorLoader in Android

Today we’ll discuss about CursorLoader in Android. Loaders have been introduced from Android 3.0 but we can also use loaders in prior versions of Android starting from Android 1.6 with the help of Android compatibility library.

There are three key benefits of using a CursorLoader:

  1. The query is handled on a background thread for you (courtesy of being built on AsyncTaskLoader) so that large data queries do not block the UI. This is something the docs recommended for you to do when you’re using a plain Cursor, but now it’s done under the hood.
  2.  CursorLoader is auto-updating. In addition to performing the initial query, CursorLoader also registers a ContentObserver with the dataset you requested and calls forceLoad() on itself when the data set changes.
  3. This results in getting async callbacks anytime the data changes in order to update the view.

Lets follow these simple steps, we’ll load contacts stored in your phone/device using cursor loader.



1. In Android Studio, go to File ⇒ New Project and fill all the details required to create a new project. When it prompts to select a default activity, select Blank Activity and proceed.

2. Open build.gradle and add recyclerview library com.android.support:recyclerview-v7:23.1.1. Your code should look like this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
}

3. Open AndroidManifest.xml and add below permission, as we are gonna read contacts.

<uses-permission android:name="android.permission.READ_CONTACTS" />

4. Implement LoaderCallbacks interface methods in your MainActivity.java

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        //TBD
    }

    @Override
    public void onLoadFinished(Loader loader, Cursor cursor) {
        //TBD
    }

    @Override
    public void onLoaderReset(Loader loader) {
        //TBD
    }
}

5. Add RecyclerView in activity layout. Open layout ⇒ activity_main.xml and paste below code.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.nthreads.cursorloader.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

6. Create a item view layout file res/layout/item_contact.xml as below:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <ImageView
        android:id="@+id/ivContactPhoto"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_alignParentTop="true"
        android:src="@drawable/avatar_jen" />

    <TextView
        android:id="@+id/tvContactName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/ivContactPhoto"
        android:layout_marginLeft="10dp"
        android:layout_marginStart="10dp"
        android:layout_toEndOf="@+id/ivContactPhoto"
        android:layout_toRightOf="@+id/ivContactPhoto"
        android:text="Jenny Li"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvContactNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvContactName"
        android:layout_marginLeft="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="5dp"
        android:layout_toEndOf="@+id/ivContactPhoto"
        android:layout_toRightOf="@+id/ivContactPhoto"
        android:text="(123) 456-7890"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>

7. Add Custom Contact Adapter for recycler view to bind contacts data. Your Code will look like this:

package com.nthreads.cursorloader.adapter;

import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.nthreads.cursorloader.R;

import static android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME;
import static android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER;
import static android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_URI;

/**
 * Created by Nauman Zubair on 1/17/2016.
 */
public class ContactAdapter extends RecyclerView.Adapter {

    Cursor cursor;
    Context mContext;

    public ContactAdapter(Context context, Cursor cursor) {
        mContext = context;
        this.cursor = cursor;
    }

    @Override
    public ContactHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_contact, null);
        return new ContactHolder(view);
    }

    @Override
    public int getItemCount() {
        return cursor.getCount();
    }

    @Override
    public void onBindViewHolder(ContactHolder holder, int position) {
        cursor.moveToPosition(position);

        holder.tvContactName.setText(cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
        holder.tvContactNumber.setText(cursor.getString(cursor.getColumnIndex(NUMBER)));
        String imageUri = cursor.getString(cursor.getColumnIndex(PHOTO_URI));

        try {
            Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), Uri.parse(imageUri));
            holder.ivContactPhoto.setImageBitmap(bitmap);
        } catch (Exception e) {
            e.printStackTrace();
            holder.ivContactPhoto.setImageBitmap(null);
        }
    }

    class ContactHolder extends RecyclerView.ViewHolder {

        ImageView ivContactPhoto;
        TextView tvContactName, tvContactNumber;

        public ContactHolder(View itemView) {
            super(itemView);
            ivContactPhoto = (ImageView) itemView.findViewById(R.id.ivContactPhoto);
            tvContactName = (TextView) itemView.findViewById(R.id.tvContactName);
            tvContactNumber = (TextView) itemView.findViewById(R.id.tvContactNumber);
        }
    }
}

8. Now open MainActivity.java and initialize loader in onCreate method of activity as below:

/**
 * Initializes the CursorLoader. The CONTACT_LOADER value is eventually passed
 * to onCreateLoader().
 */
getSupportLoaderManager().initLoader(CONTACT_LOADER, null, this);

9. Override LoaderCallbacks as below:

   /**
    * Callback that's invoked when the system has initialized the Loader and
    * is ready to start the query. This usually happens when initLoader() is
    * called. The loaderID argument contains the ID value passed to the
    * initLoader() call.
    */

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        Uri CONTACT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        return new CursorLoader(this, CONTACT_URI, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader loader, Cursor cursor) {
        cursor.moveToFirst();
        adapter = new ContactAdapter(this, cursor);
        recyclerView.setAdapter(adapter);
    }

  /**
   * Callback that's invoked when there is a change in the data source.
   */
    @Override
    public void onLoaderReset(Loader loader) {

    }


MainActivity.java 
will look like this:

package com.nthreads.cursorloader;

import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.nthreads.cursorloader.adapter.ContactAdapter;

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {

    // Identifies a particular Loader being used in this component
    private static final int CONTACT_LOADER = 0;

    RecyclerView recyclerView;
    ContactAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        /**
         * Initializes the CursorLoader. The CONTACT_LOADER value is eventually passed
         * to onCreateLoader().
         */
        getSupportLoaderManager().initLoader(CONTACT_LOADER, null, this);
    }

   /**
    * Callback that's invoked when the system has initialized the Loader and
    * is ready to start the query. This usually happens when initLoader() is
    * called. The loaderID argument contains the ID value passed to the
    * initLoader() call.
    */

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        Uri CONTACT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        return new CursorLoader(this, CONTACT_URI, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader loader, Cursor cursor) {
        cursor.moveToFirst();
        adapter = new ContactAdapter(this, cursor);
        recyclerView.setAdapter(adapter);
    }

  /**
   * Callback that's invoked when there is a change in the data source.
   */
    @Override
    public void onLoaderReset(Loader loader) {

    }
}

Now run the project and see the magic :p

cursor_loader

If you find this post helpful please don’t forget to share it, like it and leave suggestions, criticisms in comments section below. 🙂



This article has 2 comments

  1. chief Reply

    Nyc Tutorial Sir, i have a problem trying to access list image from a recyclerview that is stored in a folder on external storage, being accessed from a content provider. could you please guide me how to go about this. Do i create a separate URI to access this file, other string are stored in the database. and since my images are being i would consider external storage.

    • nthreads Reply

      Hi Chief, sorry for late reply. There are many ways to access images from external storage. All images stored there have their URI so no need to create new one. Let me know if you’re still facing the issue.

Leave a Comment

Your email address will not be published. Required fields are marked *