Fork me on GitHub

Wednesday, February 6, 2013

Scala is better than injection framework

Android programming often drives me crazy, because I have to write too much to do a simple work. Hard to write is just a little problem when it is compared to poor maintainability. For example, let us see a typical Activity implementation:

class AndroidWay extends Activity { 
    TextView name; 
    ImageView thumbnail; 
    LocationManager loc; 
    Drawable icon; 
    String myName; 

    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main);
        name      = (TextView) findViewById(R.id.name); 
        thumbnail = (ImageView) findViewById(R.id.thumbnail); 
        loc       = (LocationManager) 
                        getSystemService(Activity.LOCATION_SERVICE); 
        icon      = getResources().getDrawable(R.drawable.icon); 
        myName    = getString(R.string.app_name); 
        name.setText(myName); 
    } 
}

The method onCreate is full of boilerplate code that makes maintaining painful. To reduce them, peoples thought about injection frameworks. Roboguice is one of them. It can improve the original code to the following way:

class RoboWay extends RoboActivity { 
    @InjectView(R.id.name)             TextView name; 
    @InjectView(R.id.thumbnail)        ImageView thumbnail; 
    @InjectResource(R.drawable.icon)   Drawable icon; 
    @InjectResource(R.string.app_name) String myName; 
    @Inject                            LocationManager loc; 

    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main);
        name.setText(myName);
    } 
} 

Although it seems to be much improved, there are some drawbacks in this approach:
  • Runtime performance
  • Wiring verbose XML layout
  • Not type-safe
If you make a mistake on the type of the view widget, your app will revenge you with a horrible runtime exception. Fortunately, Scala provides a much elegant solution. Here I show a direct translation of the example above to Scala language:

class ScaloidWay extends SActivity { 
  lazy val name      = findView(TR.id.name) 
  lazy val thumbnail = findView(TR.id.thumbnail)
  lazy val icon      = R.drawable.icon.r2Drawable
  lazy val myName    = R.string.app_name.r2String
  lazy val loc       = locationManager

  onCreate { 
    contentView(R.layout.main)
    name.setText(myName)
  } 
} 

Lazy values replace injections, with no runtime performance degradation. And the code becomes much cleaner. However, this is not a final improvement. Another clutter that did not shown in this example is XML layout. It is too wordy to write a simple idea, and careful wiring with your code is mandatory. We can eliminate the source of the problem, by not writing layout in XML. The following example builds UI layout within an ordinary Scala code:

class ScaloidWay extends SActivity { 
  lazy val name      = new STextView(R.string.app_name)
  lazy val thumbnail = new SImageView()

  onCreate { 
    contentView(new SVerticalLayout += name += thumbnail)
  } 
} 

Less significant `lazy val loc` is omitted because we can use `locationManager` whenever it needed, and implicit conversion replaces the position of `myName` and `icon`.


5 comments:

  1. How about dealing with different scree sizes? Using xml layout, I could just create several layout folders and forget (partially :) ) about it (as described here: http://developer.android.com/training/basics/supporting-devices/screens.html). Using your approach, I'll need to check screen size manually.

    As for runtime type checking in findViewById, in sbt-android-plugin you could create typed version of R (named TR) and then use it like following:

    lazy val button = findView(TR.button)

    It is better than find[TextView](R.id.name) but still require guessing on what Id should I use for current layout.

    ReplyDelete
  2. Thanks for a good suggestion. I revised the post to use typed resource (TR), and mentioned that the type-safety is its merit.

    Because Scaloid layout is an ordinary Scala code, assigning different layout for each screen dimension is not too hard. However, it would be much better that the library support it systematically. Supporting multiple screens is one of the biggest issues of next version. I am thinking about a design like this:

    import Device._
    contentView = device match {
    case LARGE && LANDSCAPE => STextView("Large and landscape")
    case LARGE => SEditText("Just large")
    case SMALL && KO => STextView("작은 화면입니다")
    case _ => SButton("Otherwise")
    }

    ReplyDelete
  3. To avoid duplicate code and write code DRY it is highly recommendet to use frameworks.
    Here I found a list of many nice Android frameworks:

    http://www.1a-android.de/android-entwicklung/nutzliche-bausteine-fur-android-tools-frameworks/

    Described in German but the names are globaly unique, so the list can be used as inspiration too.

    ReplyDelete