Android
Cursor
Iteration
Database
Programming

What's the best way to iterate an Android Cursor?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Android Cursor iteration is simple conceptually but easy to misuse in ways that leak resources or hurt performance. The best pattern depends on language and API level, yet the core goals stay the same: close the cursor reliably, avoid repeated column lookups, and handle empty results safely. With modern Android code, Kotlin helpers and repository level mapping make cursor usage much cleaner.

Core Sections

Reliable iteration pattern in Kotlin

A robust cursor loop opens the query, checks moveToFirst, caches column indexes once, and closes the cursor with use.

kotlin
1val projection = arrayOf("_id", "name", "age")
2val cursor = contentResolver.query(uri, projection, null, null, "name ASC")
3
4cursor?.use { c ->
5    val nameIdx = c.getColumnIndexOrThrow("name")
6    val ageIdx = c.getColumnIndexOrThrow("age")
7
8    if (c.moveToFirst()) {
9        do {
10            val name = c.getString(nameIdx)
11            val age = c.getInt(ageIdx)
12            println("$name ($age)")
13        } while (c.moveToNext())
14    }
15}

This avoids manual close calls and prevents forgotten resource cleanup during exceptions.

Equivalent Java approach

In Java projects, use try with resources to ensure closure.

java
1String[] projection = {"_id", "name", "age"};
2
3try (Cursor c = getContentResolver().query(uri, projection, null, null, "name ASC")) {
4    if (c != null && c.moveToFirst()) {
5        int nameIdx = c.getColumnIndexOrThrow("name");
6        int ageIdx = c.getColumnIndexOrThrow("age");
7
8        do {
9            String name = c.getString(nameIdx);
10            int age = c.getInt(ageIdx);
11            Log.d("CursorDemo", name + " (" + age + ")");
12        } while (c.moveToNext());
13    }
14}

Never call getColumnIndex inside every loop iteration unless absolutely necessary.

Mapping cursor rows to domain models

A good repository design maps raw cursor rows to typed models. This keeps UI code independent from database column details.

kotlin
1data class Person(val id: Long, val name: String, val age: Int)
2
3fun mapPeople(cursor: Cursor): List<Person> {
4    val people = mutableListOf<Person>()
5    val idIdx = cursor.getColumnIndexOrThrow("_id")
6    val nameIdx = cursor.getColumnIndexOrThrow("name")
7    val ageIdx = cursor.getColumnIndexOrThrow("age")
8
9    if (cursor.moveToFirst()) {
10        do {
11            people += Person(
12                id = cursor.getLong(idIdx),
13                name = cursor.getString(nameIdx),
14                age = cursor.getInt(ageIdx)
15            )
16        } while (cursor.moveToNext())
17    }
18
19    return people
20}

Typed mapping improves testability and reduces accidental column mismatch bugs.

Asynchronous querying considerations

Cursor operations can block when dataset or provider latency is large. Run heavy queries off main thread using coroutines, Room abstractions, or paging libraries. If you still use raw cursors, place query and iteration in IO dispatcher.

kotlin
1withContext(Dispatchers.IO) {
2    contentResolver.query(uri, projection, null, null, null)?.use { c ->
3        // iterate and map
4    }
5}

Avoid long cursor loops on UI thread to prevent dropped frames and ANR risk.

Migration path toward Room

If your app still depends on many manual cursor loops, Room can reduce boilerplate and enforce compile time query validation. For legacy providers where Room is not applicable, keep cursor utilities centralized so iteration style stays consistent.

Common Pitfalls

  • Forgetting to close cursor resources and leaking file descriptors. Use use in Kotlin or try with resources in Java.
  • Looking up column indexes repeatedly inside each row iteration. Cache indexes before looping.
  • Iterating without checking moveToFirst and failing on empty result sets. Guard empty cursors explicitly.
  • Performing large cursor reads on the main thread. Move database work to background execution.
  • Spreading raw cursor access across UI classes. Centralize mapping in repository or data source layers.

Summary

  • Best cursor iteration pattern is safe closure, cached indexes, and empty result handling.
  • Kotlin use and Java try with resources are the most reliable cleanup strategies.
  • Map rows into typed models to keep code maintainable.
  • Run heavy cursor processing off main thread for responsive UI.
  • Treat cursor iteration as a data layer concern, not a view layer detail.

Additional implementation notes: keep boundaries explicit, validate assumptions with tests, and prefer maintainable conventions over clever shortcuts when designing shared code paths.


Course illustration
Course illustration

All Rights Reserved.