Sunday, November 17, 2013

Scaloid is originally made to improve my daily life that codes Android apps. Although Scaloid relies on some core design principles that is behind of automatic wrapper generator, sometimes code can be improved from some heuristics. For example, I found that my code has several functions much like this:

Soon I added a wrapper class of android.graphics.Paint to Scaloid. Then the code above is improved as:

def createMyPaint() = SPaint().color(0xffff6666).textSize(14.sp)

All of my codes and a Google search reveals that setColor() is always called after a new Paint instance is initialized. It is very natural to think about a color when we tried to paint something. So I added it as the first parameter of the method SPaint.apply(); then the code can be rewritten as:

def createMyPaint() = SPaint(0xffff6666).textSize(14.sp)

We have reached the minimum code as possible.

This feature is shipped with Scaloid 3.0-M2. You can include this version of Scaloid into a maven project by:

Friday, November 15, 2013

For the current version of Scaloid, the type of LocalServiceConnection.service is [S <: LocalService]. This value is null if the service is not yet connected. So it is exposed to the possibility of NullPointerException. To avoid the exception, users must check that the service is connected, as shown below:

The problem is that the checking is very easy to forget. So I changed the type of LocalServiceConnection.service to Option[S], so that the possibility of NPE goes away. Moreover, I added some additional helper methods that decreases clutters from your code:

The method LocalServiceConnection.apply[T](f: S => T, defaultVal: T): T returns the result of the function f if the service is connected, and returns defaultVal otherwise. This is far more cleaner than the previous one.

If we don't want to return something, and therefore we don't have any default value, we can use it as:

However, after I uploaded the app to Google Play, I received a ton of crash report:

java.lang.ExceptionInInitializerError
at scala.concurrent.impl.ExecutionContextImpl.createExecutorService(ExecutionContextImpl.scala:77)
at scala.concurrent.impl.ExecutionContextImpl.<init>(ExecutionContextImpl.scala:28)
at scala.concurrent.ExecutionContext$Implicits$.global$lzycompute(ExecutionContext.scala:63)
at scala.concurrent.ExecutionContext$Implicits$.global(ExecutionContext.scala:63)
at com.soundcorset.client.android.SoundcorsetService$Metronome.start(SoundcorsetService.scala:38)
...
Caused by: java.lang.Error: java.lang.NoSuchFieldException: parkBlocker
at scala.concurrent.forkjoin.ForkJoinPool.<clinit>(ForkJoinPool.java:2852)
... 20 more
Caused by: java.lang.NoSuchFieldException: parkBlocker
at java.lang.ClassCache.findFieldByName(ClassCache.java:510)
at java.lang.Class.getDeclaredField(Class.java:683)
at scala.concurrent.forkjoin.ForkJoinPool.<clinit>(ForkJoinPool.java:2847)
... 20 more

Scala library has its own copy of ForkJoinPool implementation, which includes dynamic invocations, that is parkBlocker in this case. Unfortunately, some Android devices does not have this method, so we've got this awful crash report.

The solution is very simple: Do not use ExecutionContext.Implicits.global. Declare a custom implicit ExecutionContext instead. For example: