Fork me on GitHub

Thursday, October 30, 2014

Scaloid 3.6 is released

Today I released Scaloid 3.6. This version contains several feature improvements:

More concise intents

Intents often has a long list of extra attributes. Your code assigning these attributes should looks like this:
new Intent().putExtra("valueA", valueA).putExtra("valueB", valueB).putExtra("valueC", valueC)
This is a shame. Using Scala macros, I made a new function put(Any*) on Intent, that can be used like this:
new Intent().put(valueA, valueB, valueC)


Intents can be started

To start an activity from the intent above, we are writing like this:
startActivity(SIntent[MyActivity].put(valueA, valueB, valueC))
This is concise, but not really readable. From this version, we can rewrite it:
new Intent().put(valueA, valueB, valueC).start[MyActivity]
This looks more natural because it can be translated one-to-one in plain English:
Create a new Intent, and put values A, B, and C. Then, start the MyActivity.


Wrap or fill

I found that I am using layout properties wrap and fill very frequently. Because layout properties should written in <<...>> blocks, we have a lot of <<.wrap.>> and <<.fill.>> in our code. This is completely valid, but not very pleasing. Because Scaloid is not stingy with providing shorthands, I dropped out <<...>> from it.

Now we can specify 'wrap-ness' of a TextView like this:
textView.wrap
This looks trivial at first. However, think about how many times you are using it.

Additionally, we provide functions fw and wf, that is equivalent to <<(FILL_PARENT, WRAP_CONTENT).>> and <<(WRAP_CONTENT, FILL_PARENT).>> respectively.


Drops support Scala 2.10

Scaloid 3.6 is built on Scala 2.11.3, so you can use it with other Scala 2.11.x versions. From this version, I drops Scala 2.10 support to use full potential of recent improvements on macros.


Building Scala 2.11.3 on Android

I've found that building Android apps with Scala 2.11.3 (and 2.11.4) results more proguard warning, that prevents Android projects from being built. The error messages are:
Warning: scala.collection.SeqLike$$anon$1: can't find enclosing method 'scala.collection.mutable.Map occCounts(scala.collection.SeqLike,scala.collection.Seq)' in program class scala.collection.SeqLike
Warning: scala.collection.immutable.MapProxy$$anon$1: can't find enclosing method 'scala.collection.immutable.MapProxy newProxy(scala.collection.immutable.MapProxy,scala.collection.immutable.Map)' in program class scala.collection.immutable.MapProxy
Warning: scala.collection.immutable.SetProxy$$anon$1: can't find enclosing method 'scala.collection.immutable.SetProxy newProxy(scala.collection.immutable.SetProxy,scala.collection.immutable.Set)' in program class scala.collection.immutable.SetProxy
Warning: scala.collection.parallel.ParMapLike$$anon$3: can't find enclosing method 'scala.collection.parallel.IterableSplitter scala$collection$parallel$ParMapLike$$keysIterator(scala.collection.parallel.ParMapLike,scala.collection.parallel.IterableSplitter)' in program class scala.collection.parallel.ParMapLike
Warning: scala.collection.parallel.ParMapLike$$anon$4: can't find enclosing method 'scala.collection.parallel.IterableSplitter scala$collection$parallel$ParMapLike$$valuesIterator(scala.collection.parallel.ParMapLike,scala.collection.parallel.IterableSplitter)' in program class scala.collection.parallel.ParMapLike
Note: there were 1 references to unknown classes.
      You should check your configuration for typos.
      (http://proguard.sourceforge.net/manual/troubleshooting.html#unknownclass)
Note: there were 2 classes trying to access generic signatures using reflection.
      You should consider keeping the signature attributes
      (using '-keepattributes Signature').
      (http://proguard.sourceforge.net/manual/troubleshooting.html#attributes)
Warning: there were 5 unresolved references to program class members.
         Your input classes appear to be inconsistent.
         You may need to recompile the code.
         (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)
[trace] Stack trace suppressed: run last android:proguard for the full output.
To build it successfully, add "-keepattributes Signature", "-dontwarn scala.collection.**" to your proguard options. Please refer to this commit log on hello-scaloid-git project that illustrates this patch.


Using Scaloid 3.6.1

Scaloid is released to central maven repository.

Update: Scaloid 3.6 was built on Scala 2.11.3, that has some bug on its compiler. I rebuild Scaloid on Scala 2.11.4 and released it as 3.6.1

For a maven project:
<dependency>
    <groupId>org.scaloid</groupId>
    <artifactId>scaloid_2.11</artifactId>
    <version>3.6.1-10</version>
</dependency>
or for an sbt project:
libraryDependencies += "org.scaloid" %% "scaloid" % "3.6.1-10"

Monday, May 5, 2014

Scaloid 3.4 is released

Today I released Scaloid 3.4. This version contains no new features, but reflects important policy changes:

Recommends Scala 2.11

From this version, Scaloid will be released for Scala 2.11 only. But don't worry, you can easily build your own with Scala 2.10.

Drops Froyo support

Last month, we found an incompatibility between Android API 8 and Scala 2.11. From this version, we drop support Froyo.


Scaloid is released to central maven repository.

For a maven project:
<dependency>
    <groupId>org.scaloid</groupId>
    <artifactId>scaloid_2.11</artifactId>
    <version>3.4-10</version>
</dependency>
or for an sbt project:
libraryDependencies += "org.scaloid" %% "scaloid" % "3.4-10"

Tuesday, April 22, 2014

Android API 8 is incompatible with mutable collections from Scala 2.11

I've found that newly introduced class scala.collection.mutable.AnyRefMap and mutable.LongMap are using java.util.Arrays.copyOf(...), that is available only on Android API level 9 and above. This means that your Android app might not work with Froyo devices if you build it with Scala 2.11. Because the current market share of the API level < 9 is less than 1 percent, this limitation is not serious.

However, if you stick to support older Android devices, here is a workaround:
  • Add -dontwarn scala.collection.mutable.** in your proguard settings.
  • Do not use scala.collection.mutable.LongMap and mutable.AnyRefMap. Make sure these classes not be called in transitive way.

Sunday, April 20, 2014

Scaloid 3.3 is released

Today I released Scaloid 3.3. This version achieves some feature enhancements including:

Scala 2.11 support

Scala 2.11 is out! We release this version of Scaloid in both of Scala 2.10 and 2.11.

Press-and-Hold action

We support press-and-hold action listener for any subtype of android.view.View object.

Any return type of event listener is accepted

Please refer to issue #78

Scaloid is released to central maven repository.

For a maven project:
<dependency>
    <groupId>org.scaloid</groupId>
    <artifactId>scaloid_2.10</artifactId>
    <version>3.3-8</version>
</dependency>
or for an sbt project:
libraryDependencies += "org.scaloid" %% "scaloid" % "3.3-8"

Saturday, April 19, 2014

Press-and-hold action on Android

Sometimes we need to receive repeated callback when a user pressing a button continuously. For example, when we make a number picker, press-and-hold on "+" button will constantly increase the number.

Android does not support this explicitly, but we can handle this behavior with postDelayed event. Here is a link to a code snippet doing this:


This works fine, but a little remaining problem is that this code should be encapsulated to be reused in other places. I wrote the following trait doing the encapsulation:

trait PressAndHoldable {
  def basis: android.view.View

  class PressAndHoldListener(interval: Int, onPressed: () => Unit) extends View.OnTouchListener with View.OnLongClickListener {
    var autoIncrementing: Boolean = false
    private val repeatUpdateHandler = new android.os.Handler()

    override def onTouch(v: View, event: MotionEvent): Boolean = {
      if (event.getAction == MotionEvent.ACTION_UP && autoIncrementing) {
        autoIncrementing = false
      }
      false
    }

    override def onLongClick(p1: View): Boolean = {
      autoIncrementing = true
      repeatUpdateHandler.post(new RptUpdater)
      false
    }

    class RptUpdater extends Runnable {
      override def run() {
        if (autoIncrementing) {
          onPressed()
          repeatUpdateHandler.postDelayed(this, interval)
        }
      }
    }
  }

  def onPressAndHold(interval: Int, onPressed: => Unit) {
    val listener = new PressAndHoldListener(interval, () => onPressed)
    basis.setOnTouchListener(listener)
    basis.setOnLongClickListener(listener)
  }
}

This trait is added on Scaloid 3.3 release. So you can just use onPressAndHold method for any android.view.View objects:
val num = STextView("0")
SButton("Increase").onPressAndHold(100, num.text = (num.text.toString.toInt + 1).toString)

Saturday, February 1, 2014

Scaloid 3.2 is released

Today I released Scaloid 3.2. This version achieves some feature enhancements including:

Properly handle TableLayout

https://github.com/pocorall/scaloid/issues/68

Enriched android.database.Cursor

Now we can access database in more functional style.

px2sp and px2dip

We can simply convert pixel unit into sp unit or dip unit.
32.px2dip  // convert 32 pixel into dip unit

Scaloid is released to central maven repository.

For a maven project:
<dependency>
    <groupId>org.scaloid</groupId>
    <artifactId>scaloid_2.10</artifactId>
    <version>3.2-8</version>
</dependency>
or for an sbt project:
libraryDependencies += "org.scaloid" %% "scaloid" % "3.2-8"

Simple enhancements on accessing Android built-in Sqlite

Functional programming rocks. However, legacy Java libraries prevent us to stay in such an ideal world. The job objective of Scaloid is to help write Android code in more elegant style. Although Scaloid doing good job on layout building, I haven't do much things about databases yet.

There are many DB frameworks for Scala. If you writing an app relying on DB extensively, I recommend to use one of them. But when you are using the built-in Sqlite just for storing some casual data, it is not a bad idea that using Android APIs for database access. The biggest problem in this case is that the code is not neat, because of its API architecture. Let's see a common example:

def readDailyPractice(query: String, params: Array[String]) = {
  var dailyTime: List[DailyPractice] = List()
  val c = getReadableDatabase.rawQuery(query, params)
  try {
    while (c.moveToNext()) {
      dailyTime = dailyTime :+ 
        new DailyPractice(c.getString(0), c.getString(1), c.getLong(2))
    } 
  } finally c.close()
  dailyTime
}

We have to use variable for the return object because the android.database.Cursor does not provide Scala-compatible iterator. Another clutter is close() call in try-finally section. This can be reduced in more functional-style:

def readDailyPractice(query: String, params: Array[String]) = 
  getReadableDatabase.rawQuery(query, params).closeAfter(_.map(c => 
    new DailyPractice(c.getString(0), c.getString(1), c.getLong(2)).toList)
  )

By declaring implicit def cursor2RichCursor(c: Cursor) = new RichCursor(c), or extending the trait org.scaloid.common.DatabaseImplicits to your class, The android.database.Cursor implicitly converted into RichCursor, which implements Iterable[Cursor]. Now, we can use many useful iterator methods such as map, foreach, and foldLeft.

I also introduce a function closeAfter[T](body: RichCursor => T):T that closes the cursor after evaluating its parameter.

One of the important thing that can be missed is to convert the mapped result into a List. Iterator.map returns another Iterator that evaluate the original iterator(RichCursor in this case) on demand. The implementation of Iterator.map looks like this:

def map[B](f: A => B): Iterator[B] = new AbstractIterator[B] {
  def hasNext = self.hasNext
  def next() = f(self.next())
}

So if we missed to convert the iterator to List or other form, newly created Iterator generated from the mapping will evaluate the Cursor that is already closed, and vomit the exception. This issue is too detailed, and very easy to miss at first. What we just need is iterate each rows and map a row with a domain object, and forget about cursor. So I created a wrapper function that does this mission simply:

def readDailyPractice(query: String, params: Array[String]) = 
  getReadableDatabase.rawQuery(query, params).orm(c => 
    new DailyPractice(c.getString(0), c.getString(1), c.getLong(2)))

As the name of the function implies, it does very simple form of Object-Relation-Mapping.

In other cases, we often need just one record from the query result. For example, getting count of something, or retrieving some column about a particular user. Even such a simple requirement, we have to write a verbose code as:

def readOneRecord(query: String, params: Array[String], default: String) = 
  try {
    val c = getReadableDatabase.rawQuery(query, params)
    if (c.moveToFirst()) c.getString(0) else default
  } finally c.close()

I created a helper method toString(defaultVal) for this purpose. We can rewrite the code above as:

def readOneRecord(query: String, params: Array[String], default: String) = 
  getReadableDatabase.rawQuery(query, params).toString(default)

Other accessors, toShort, toLong, toInt, toFloat, and toDouble is available as well.

All of these magic can be done by the implicitly converted class RichCursor. The source code of RichCursor is very simple.

class RichCursor(c: Cursor) extends Iterable[Cursor] {
  def iterator = new CursorIterator

  class CursorIterator extends Iterator[Cursor] {
    def hasNext = c.getPosition < c.getCount - 1

    def next() = {
      c.moveToNext()
      c
    }
  }

  def closeAfter[T](body: RichCursor => T) = try body(this) finally c.close()

  def orm[T](body: Cursor => T) = closeAfter(_.map(body).toList)

  def toLong(default: Long): Long = closeAfter(csr => if (c.moveToFirst()) c.getLong(0) else default)

  // definitions of toString, toShort... is straightforward
}

This class is also available on Scaloid 3.2.1