Fork me on GitHub

Friday, August 14, 2015

Scaloid 4.0 is released

Today I release Scaloid 4.0, which includes lots of improvements.

Incompatible changes

Scaloid 4.0 is best with Android API Level 16, while still supports Level 10

Scaloid 4.0 distribution is compiled with Android API Level 16, still retaining compatibility with Level 10. Accessor methods and deprecation warning is based on API Level 16, so you have to carefully check the availability of API if you target lower version of Android. In our experience on building an App for Gingerbread, currently we found no obstruction that prevents building a Scaloid app for older devices.

To compile with Scaloid 4.0, you have to specify build time Android API in project.properties file as android-16 or higher.

LocalServiceConnection

Usual use case of LocalServiceConnection is:
val service = new LocalServiceConnection[MyService]
//...
service {
  myService => // do something with myService
}
The implementation of apply() is changed to:
def apply[T](f: S => Unit): Unit = service.fold(onConnected(f))(f)
The block will be executed later if the service is not connected yet. Formally it was just did nothing when the service is not connected yet. This behavior now becomes ifAvailable. Then all the code using apply(...) should be changed to ifAvailable(...).

SArrayAdapter

Constructor parameter of SArrayAdapter is now java.util.List.

spinnerDialog

spinnerDialog returns Future[ProgressDialog].

End of incompatible changes


More convenient press-and-hold action

We provided the press-and-hold action callback from the last release. The sample code below increases the number on the TextView in every 100 milliseconds:
val num = STextView("0")
SButton("Increase").onPressAndHold(100, num.text = (num.text.toString.toInt + 1).toString)
The press-and-holds has separate callback from that of clicks. Often the behavior for a click and press-and-hold are the same. In this case, we have to assign it individually:
val num = STextView("0")
SButton("Increase", num.text = (num.text.toString.toInt + 1).toString)
.onPressAndHold(100, num.text = (num.text.toString.toInt + 1).toString)
This kind of repetition is not looks good. From Scaloid 4.0, we introduce a shorter representation:
val num = STextView("0")
SButton("Increase", num.text = (num.text.toString.toInt + 1).toString, 100)

Cleaner and type-safe way to access SharedPreference

SharedPreference can be accessed in this way:
val executionCount = preferenceVar(0) // default value 0
val ec = executionCount() // read
executionCount() = ec + 1 // write
executionCount.remove() // remove
Refer to this post for more details:
http://blog.scaloid.org/2015/07/type-safe-sharedpreference.html

`here` method

To attach a view widget to a layout, we have two major way to do it. One is using apply method of companion object of views:
new SLinearLayout {
  SButton("Hello") textColor Color.RED
}
The other is creating object with new keyword, and assign it to the layout using += method:
lazy val button = new SButton("Hello")

onCreate {
  contentView = new SLinearLayout {
    this += button
    button textColor Color.RED
  }
}
In Scaloid 4.0, we have another choice:
lazy val button = new SButton("Hello")

onCreate {
  contentView = new SLinearLayout {
    button.here textColor Color.RED
  }
}
As you see, this += is abbreviated with .here. Because it returns SButton, method chaining is available.

Oftentimes, style is applied to widgets, and sometimes we want avoid it for some widget. We provide hereWithoutStyle method:
lazy val button = new SButton("Hello") textColor Color.RED
lazy val redBtn = new SButton("Hello") textColor Color.RED

contentView = new SLinearLayout {
  style {
    case b: Button => b textColor Color.BLUE
  }
  button.here // blue
  redBtn.hereWithoutStyle // red
}
here method can also be applied to layouts:
contentView = new SVerticalLayout {
  new SLinearLayout {
    SButton("1")
    SButton("2")
  }.here
  SButton("3")
}

Thursday, August 6, 2015

StateListDrawable DSL for cleaner button styles.

XML is wordy and not programmable. One of the major goal of Scaloid is to replace XMLs with Scala code, to be more concise, type-safe and programmable. Scaloid 4.0-RC2 provides a new way to build StateListDrawable without XML. For example, given the XML drawable selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item   android:state_checked="true"
            android:drawable="@drawable/drumset_on"/>
    <item   android:state_checked="false"
            android:drawable="@drawable/drumset_off"/>
</selector>

This can be written with SStateListDrawable as shown here:
new SStateListDrawable {
  +=(R.drawable.drumset_off, -SELECTED)
  +=(R.drawable.drumset_on, SELECTED)
}
It is definitely much cleaner and programmable. For example, we can write a program that returns a single-colored drawable for buttons:
def c(color: Int) = new ColorDrawable(color)

def btn(normal:Int, pressed:Int) = new SStateListDrawable {
  +=(c(pressed), PRESSED)
  +=(c(Color.LTGRAY), -ENABLED)
  +=(c(normal))
}
We pass normal color and pressed color in an Int type color code. We can further think that pressed color can be automatically determined by a normal color; It is really easy to implement:
def btn(normal:Int): SStateListDrawable = btn(normal, pressedColor(normal))
SStateListDrawable is included in Scaloid library, and the implementation of btn and pressedColor implemented in org.scaloid.util.Styles, which is included in Scaloid-util package.

The implementation is quite straightforward, take a look at the code, and you can easily have a conception about this idea:

Source of SStateListDrawable (Scaloid package)
Source of Styles (Scaloid-util package)

Monday, July 13, 2015

Type-safe SharedPreference

Scaloid provides a concise way to access SharedPreference using type dynamic of Scala language. A sample code that demonstrate it looks like this:
val pref = Preferences()
val ec = pref.executionCount(0) // read with default value 0
pref.executionCount = ec + 1 // write
pref.remove("executionCount") // remove
It is clearly better than old-Android-API, but it still has some limitations:
  • No compile-time name checks
    If there are a typo on the key name of the preferences, compiler does not warn you. It will be a nightmare if some preference name is used in multiple places and only one has a typo.
  • Key names should be a Scala symbol name
    For example, a key.name@with:special#chars cannot be accessed with Scala type dynamic
  • Inconsistencies with method and preference access
    In the above example, pref.remove(...) is a pre-defined method call, while pref.executionCount(...) is a preference access.
Scaloid 4.0-RC2 contains a new way to access Preferences. Let's look into it:
val executionCount = preferenceVar(0) // default value 0
val ec = executionCount() // read
executionCount() = ec + 1 // write
executionCount.remove() // remove
It is cleaner, performs compile-time validation, and better semantics. Scala macro accesses the value name to be assigned, executionCount in this example. If you want a keyname has special characters, we provide an alternative:
val executionCount = preferenceVar("execution.count", 0)
Preference values does not have a dependency on android.context.Context when it is created. A Context is only needed when it is actually being accessed (e.g. reading, writing, removing). So, it can be a property of plain object (static method in Java terms), and can be defined only once:
object Prefs {  // It ensures compile-time name checking
  val executionCount = preferenceVar(0)
  val showTips = preferenceVar("show-tips", true)
}

class MyActivity extends SActivity {
  // access it anywhere having implicit Context
  if(Prefs.showTips()) displayTips() 
}
Let's compare it with the same code written in plain-old Android API:
object Prefs { // No idea about types, awful naming convention
  val EXECUTION_COUNT = "executionCount"
  val SHOW_TIPS = "show-tips"
}

class MyActivity extends SActivity {
  // Wordy and not type-safe
  if(defaultSharedPreference.getBoolean(Prefs.SHOW_TIPS, true)) displayTips() 
}
The full source code is listed in the Scaloid github repository. This is tested on a millionth-downloaded production app, Soundcorset.

Friday, April 17, 2015

Scaloid 4.0 RC1 released

Today, I released a release candidate of Scaloid 4.0. This version has several feature additions and a notable change about versioning:

Scaloid 4.0 is best with Android API Level 16, while still supports Level 10

Scaloid 4.0 distribution is compiled with Android API Level 16, still retaining compatibility with Level 10. Accessor methods and deprecation warning is based on API Level 16, so you have to carefully check the availability of API if you target lower version of Android. In our experience on building an App for Gingerbread, currently we found no obstruction that prevents building a Scaloid app for older devices.

Notes for incompatible changes

To compile with Scaloid 4.0, you have to specify build time Android API in project.properties file as android-16 or higher.


Scaloid is released to central maven repository.

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

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. These idioms makes sense in terms of consistency, 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 rebuilt 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.