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:
- 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.
- 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.
- 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
If you find this post helpful please don’t forget to share it, like it and leave suggestions, criticisms in comments section below. 🙂