Cursor Adapter in Android

Cursor adapters provide a high-performance way to scroll through long lists of data that are stored in SQLite.The consuming code must define an SQL query in a Cursor object and then describe how to create and populate the views for each row.

The CursorAdapter fits in between a Cursor (data source from SQLite query) and the ListView (visual representation) and configures two aspects:

  • Which layout template to inflate for an item
  • Which fields of the cursor to bind to views in the template

The cursor query must have an integer column _id for the CursorAdapter to work. If the underlying table does not have an integer column named _id then use a column alias for another unique integer in the query that makes up the cursor.

Creating Cursor

RawQuery is used to turn an SQL query into a Cursor object. The column list that is returned from the cursor defines the data columns that are available for display in the cursor adapter.

db = SQLiteDatabase.OpenDatabase(path);
var cursor = db.RawQuery("SELECT * FROM myDatabase", null); // cursor query
StartManagingCursor(cursor);
// use either SimpleCursorAdapter or CursorAdapter subclass here!`

You will always need to start and stop the cursor when you want to use it. This can be handled through StartManagingCursor and StopManagingCursor

Important Note

StartManagingCursor and StopManagingCursor are now depreciated and it is advised not to use them in production code. Instead CursorLoader are highly preferred. You can understand Cursor Load Manager and how it works through this blog series.

In general we have two ways of using Cursor Adapter in Android which are
SimpleCursorAdapter or a subclass of CusorAdapter to display rows in a ListView.

a- Simple Cursor Adapter

SimpleCursorAdapter is like the ArrayAdapter, but specialized for use with SQLite.

The parameters for the SimpleCursorAdapter constructor are:

Context – A reference to the containing Activity.

Layout – The resource ID of the row view to use.

ICursor – A cursor containing the SQLite query for the data to display.

From string array – An array of strings corresponding to the names of columns in the cursor.

To integer array – An array of layout IDs that correspond to the controls in the row layout. The value of the column specified in the from array will be bound to the ControlID specified in this array at the same index.

The from and to arrays must have the same number of entries because they form a mapping from the data source to the layout controls in the view.

// which columns map to which layout controls
string[] fromColumns = new string[] {"name"};
int[] toControlIDs = new int[] {Android.Resource.Id.Text1};
// use a SimpleCursorAdapter
listView.Adapter = new SimpleCursorAdapter (this, Android.Resource.Layout.SimpleListItem1, cursor,
       fromColumns,
       toControlIDs);

The main limitation of this option is that it can only bind column values to display controls, it does not allow you to change other aspects of the row layout (for example, showing/hiding controls or changing properties).

b- Subclassing Cursor Adapter

A CursorAdapter subclass has the same performance benefits as the SimpleCursorAdapter, but it also gives you complete control over the creation and layout of each row view.

We need to override two methods here
BindView – Given a view, update it to display the data in the provided cursor.

NewView – Called when the ListView requires a new view to display. The CursorAdapter will take care of recycling views (unlike the GetView method on regular Adapters).

View Holder Improvement

The cursor adapter itself supports recycling views as we said but if you want to improve it more , you can then apply the view holder pattern. This is will save us the cost of the multiple FindViewById calls that should be made each time the view is retrieved.

To make things very clean , I'm providing a full sample code for a tweetlistadapter that subclasses CursorAdapter

		
	public class TweetListAdapter : CursorAdapter
	{

		Activity activity;
		ICursor appliedCursor;
		public TweetListAdapter(Activity context , ICursor resultCursor)
			: base(context, resultCursor)
		{
			this.activity = context;
			this.appliedCursor = resultCursor;
		}

		
		public override void BindView(View view, Context context, ICursor cursor)
		{
			TweetViewHolder viewHolder = (TweetViewHolder) view.Tag;
			
			//For a cleaner code , I'm creating a tweet object that has the values for all properties needed for binding
			Tweet selectedTweet = GetselectedTweet(cursor);

			//Bind Data

			viewHolder.tweetTextView.Text = selectedTweet.TweetText;
			viewHolder.tweetDateTextView.Text = selectedTweet.Date.ToString(CultureInfo.CurrentCulture);
			//Bind the rest of the properties
			

			//Retweet Button

			viewHolder.detailsButton.Click += ((sender, args) =>
			{
				//Event Handling Logic
			});

		}
		public override View NewView(Context context, ICursor cursor, ViewGroup parent)
		{
			TweetViewHolder viewHolder = new TweetViewHolder();


			View convertView = activity.LayoutInflater.Inflate(Resource.Layout.TweetListRowItem, parent, false);
			viewHolder.InitializeControls(convertView);
			convertView.Tag = viewHolder;

			return convertView;
		}
		
		
		private Tweet GetSelectedItem(ICursor cursor)
		{
			return new FSOrderBinder
			{
				TweetText = (string)GetColumnValue(typeof(String), "TweetText"),
				Status = (int)GetColumnValue(typeof(int), "Status"),
				Date = (Double)GetColumnValue(typeof(double), "Date"),
				//Get the remaining props
			};
		}

		#region Cursor Data Extraction Helpers
		private int GetColumnIndex(string columnName)
		{
			return appliedCursor.GetColumnIndex(columnName);
		}
		
			private object GetColumnValue(Type type , string columnName)
		{
			if (type == typeof(String))
			{
				return appliedCursor.GetString(GetColumnIndex(columnName));

			}
			else if (type == typeof(int))
			{
				return appliedCursor.GetInt(GetColumnIndex(columnName));
			}
			else if (type == typeof(double))
			{
				return appliedCursor.GetDouble(GetColumnIndex(columnName));

			}

			return null;
		}
		#endregion
		
	class TweetViewHolder : Java.Lang.Object
	{

		public TextView tweetTextView;
		public TextView authorTextView;
		public ImageView profilePicImage;
		public TextView tweetDate;
		public ImageView retweetButton;


		public void InitializeControls(View view)
		{
			 retweetButton = view.FindViewById<ImageView>(Resource.Id.RetweetButton);
			 tweetTextView= view.FindViewById<TextView>(Resource.Id.TweetTextView);
			 profilePicImage = view.FindViewById<ImageView>(Resource.Id.ProfilePicImage);
			 authorTextView= view.FindViewById<TextView>(Resource.Id.AuthorTextView);
			 tweetDate = view.FindViewById<TextView>(Resource.Id.TweetDate);
		}
	}
		

Which one should I use ?

Simple answer , go for the second choice.

Reason:
First , implementing CursorAdapter is not so hard at all as we saw. It is more flexible, SimpleCursorAdapter is simple in use and that is all.

Resources

1- Using Cursor Adapter - Xamarin Tutorials

2- Populating a ListView with a CursorAdapter

3- Custom CursorAdapter and why not use SimpleCursorAdapter

Subscribe to Learn With Passion

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe