Ever wanted to iterate over Android cursor in the scala way ? This article will explain how with scala 2.11 we can write a library to do this awesome calls.
import AugmentedCursor._ val uri = Uri.parse("content://com.android.calendar/events") val cursor = getContentResolver().query(uri, Array("calendar_id", "title", "description"), null, null, null) for((calendar_id, title, description) <- cursor.toIterator[Int, String, String]) { /// ... Do something with calendar_id, title /// and description for each entry }
This is so much better than writing complicated code, and besides, it provide types for the iteration variables !
Here is how we can make this magic happen.
First, we need to create an implicit class so that the method .toIterator[Int, String, String] can be defined.
object AugmentedCursor { implicit class RichCursor(c: Cursor) { def toIterator[T, U, V] = ??? } }
Now, the method toIterator should return an Iterable[(T, U, V)] to be able to write the initial code. Thus:
implicit class RichCursor(c: Cursor) { def toIterator[T, U, V] = new Iterable[(T, U, V)] { def iterator = ??? } }
The iterator itself must be an iterator over the triplet. Since we will only call it once and then close it, we can inline its construction.
implicit class RichCursor(c: Cursor) { def toIterator[T, U, V] = new Iterable[(T, U, V)] { def iterator = new Iterator[(T, U, V)] { def hasNext: Boolean = ??? def next: (T, U, V) = ??? } } }
The hasNext
method can compare the position of the cursor against the length of the result. Since during iterations, hasNext is called only once per iteration, we can close the cursor when there is nothing more to fetch.
def hasNext: Boolean = { val res = c.getPosition < c.getCount - 1 if(!res) c.close() res }
The method next is as simple, it will move the cursor to one place and then extract the right positions.
def next: (T, U, V) = { c.moveToNext() ??? }
Here we have a new problem. There is no such method as c.get[T](0)
(take the first element of the cursor, and cast it to T). To the contrary, there are some methods such as c.getString
, c.getLong
, c.getInt
, etc.
This is the time to set up a constraint: T, U and V can only be of type that the cursor can extract. We can enfore this by requiring that there exist implicit objects which can extract the type T out the cursor. For that, we define the trait Iter and two implicit objects implementing it, in the top-level object:
trait Iter[T] { def apply(c: Cursor, i: Int): T implicit object IterS extends Iter[String] { def apply(c: Cursor: i: Int):String = c.getString(i) } implicit object IterI extends Iter[Int] { def apply(c: Cursor: i: Int):Int = c.getInt(i) }
Nice, but how to be able to include them now in the next
function? Simply, we can use implicitly[Iter[T]]
to retrieve the implicit object and call its apply method. Sorry, we cannot use the syntactic sugar of removing the word "apply" since the method implicitly does not take parameters and this would be ambiguous. Thus:
def next: (T, U, V) = { c.moveToNext() (implicitly[Iter[T]].apply(c,0), implicitly[Iter[U]].apply(c,1), implicitly[Iter[V]].apply(c,2)) }
Now the compiler complains that there is no guarantee that there is an implicitly[Iter[T]]
. How can we ensure that so that T is restrained? There is a funcitonality in scala: implicit parameters. If the compiler can resolve them, no need to specify them ! This means that the method toIterator[T, U, V] introducing the three type parameters requires the implicit objects to work, that is:
def toIterator[T, U, V](implicit objT: Iter[T], objU: Iter[U], objV: Iter[V])
There is also an even better syntax for that in scala, syntactic sugar:
def toIterator[T: Iter, U: Iter, V: Iter]
What's next? Abstraction ! We did it for three parameters, but perhaps we want to have one, two or more ! The refactoring is left as an exercise for the reader. For the impatient reader, the solution is below:
You need to to get the syntactic sugar above. Voilà !
// AugmentCursor.scala import android.database.Cursor object AugmentedCursor { trait Iter[T] { def apply(cursor: Cursor, index: Int): T } implicit object IterInt extends Iter[Int] { def apply(c: Cursor, i: Int) = c.getInt(i) } implicit object IterString extends Iter[String] { def apply(c: Cursor, i: Int) = c.getString(i) } implicit class RichCursor(c: Cursor) { private def implicitIterator[T](f: => T) = new Iterator[T] { def hasNext = { val res = c.getPosition < c.getCount - 1 if(!res) c.close() res } def next() = { c.moveToNext() f } } private def implicitIter[T](f: => T) = new Iterable[T] { def iterator = implicitIterator[T] { f } } def toIterator[T : Iter] = implicitIter { implicitly[Iter[T]].apply(c, 0) } def toIterator[T : Iter, U: Iter] = implicitIter { (implicitly[Iter[T]].apply(c, 0), implicitly[Iter[U]].apply(c, 1)) } def toIterator[T : Iter, U: Iter, V: Iter] = implicitIter { (implicitly[Iter[T]].apply(c, 0), implicitly[Iter[U]].apply(c, 1), implicitly[Iter[V]].apply(c, 2)) } }