Sofia

Introduction
Philosophia Sofia
Chapter 0
Eclipse Install with ADT
Chapter 1
Getting Started with Android
Chapter 2
Structure of an Android Application
Chapter 3
Basic GUI-Driven Apps
Chapter 4
Testing GUI Apps
Chapter 5
Advanced Graphics
Gallery
Student Work Gallery
Appendix A
Sofia API
Appendix B
Demos and Downloads

Introduction
Philosophia Sofia

Below are just a few of the motivating ideas that make up the Philosophia Sofia.

Avoid Tradition for Tradition's Sake

Java is a statically-typed language, which imposes certain design patterns and conventions on the programmer. For example, sometimes polymorphism through virtual methods called on a base class reference is not enough to solve a problem; runtime type checks (instanceof) and clunky static downcasts are needed to get at specific data from an object returned by the system. (For example, think about retrieving a reference to a widget of a specific type by calling findViewById.)

Likewise, event handling often takes place using listener interfaces, which have their own set of limitations. Consider handling the event that is fired when an item is clicked in a ListView. Some object in your system must implement the OnItemClickListener interface. You can do this in the activity class that owns the view, but this forces you to only have one method for all list views that you might have in that activity. Named or anonymous inner classes remove this limitation, but are awkward in other ways.

We have chosen to free ourselves of the shackles above, eschewing tradition when it would only serve to slow down the development process or provide little value in terms of solving an actual task in the problem domain. We strive not only to provide a simpler way for students to write Android apps, but a better way. The framework should handle as much of the "glue" as possible, leaving the programmer to focus much more on the meaningful app logic.

Breaking Through the Ceiling

Many software frameworks used to teach introductory computer science courses cause students to hit a ceiling after reaching a certain degree of proficiency, at which point the framework becomes unusable. Many of these educational frameworks support a very restricted subset of functionality and applications developed with them end up looking like "toys" or otherwise fail to meet the standards of real applications on the target platform, which demotivates students.

These concerns were at the front of our minds when we developed Sofia. We don't want students to feel forced into the Sofia philosophy; we want them to find comfort in it and appreciate it on its own. Students who have done Android programming before using Sofia have reacted very positively about the way the framework simplifies and abstracts many of the lower-level aspects of the platform.

By not locking users into a particular programming model, we ensure that it is always possible to fall back to the standard Android API if Sofia doesn't support a feature that students would need. Unlike a lot of other tools and frameworks, Sofia doesn't provide a totally different or alien set of building blocks for constructing Android applications; it just provides bigger blocks. This makes it possible for Sofia to be used not only by students but by professional Android developers as well. Students will learn skills that they can build on after class, not skills that they have to unlearn or replace.

Convention Over Configuration

Here are some ways that Sofia applies convention over configuration principles to Android programming:

Task Using Traditional Android APIs Using Sofia
Loading layouts Your activity must explicitly call setContentView to load a layout resource. For subclasses of sofia.app.Screen, the layout resource is loaded automatically based on the name of the class. For example, a class named FooScreen would load res/layout/fooscreen.xml, if found.
Accessing widgets Your activity must explicitly call findViewById to look up a widget in your layout. This method returns View, so you must then explicitly downcast it to the actual type. For subclasses of sofia.app.Screen, private fields with names that match IDs in the auto-loaded layout will be automatically filled with references to those widgets, as long as the types are compatible.
Handling events Your code must explicitly implement listener interfaces and then call setter methods that register the listeners with the widgets you are interested in. Events are dispatched automatically to methods that are named based on the ID of the widget and the type of event you are interested in. For example, myListItemClicked would be called when an item is clicked in a ListView with the ID myList.

Use Strong Typing Intelligently

Imagine that you had an object-oriented graphics framework like the one in Sofia. It has a Shape class, which then has subclasses for specific kinds of shapes, like RectangleShape and OvalShape. Imagine further that when these shapes are on the screen, they can receive notifications when they collide with other shapes. In order to polymorphically handle collisions between all kinds of shapes, a traditional Java library might have the user override a method like this in their Shape subclass:

public void onCollisionWith(Shape otherShape)
{
    // ...
}
        

But we're not done yet. At this point, we don't know what type of shape the other one actually is. If we're writing a game, we certainly want to know this; collisions between friendly characters would be handled different than collisions between a friendly character and a monster. This means we need to do runtime type checks by hand:

public void onCollisionWith(Shape otherShape)
{
    if (otherShape instanceof GoodGuy)
    {
        GoodGuy goodGuy = (GoodGuy) otherShape;
        // Do something with goodGuy...
    }
    else if (otherShape instanceof BadGuy)
    {
        BadGuy badGuy = (BadGuy) otherShape;
        // Do something with badGuy...
    }
}
        

This kind of boilerplate code doesn't contribute much real value to the solution; in fact, it detracts from the legibility of the solution. Wouldn't it be nice if you could just say what you meant in the first place, like this?

public void onCollisionWith(GoodGuy goodGuy)
{
    // Do something with goodGuy...
}

public void onCollisionWith(BadGuy badGuy)
{
    // Do something with badGuy...
}
        

Sofia lets you do exactly this! Polymorphism in Java is "single dispatch", meaning that only the type of the object receiving the method call is considered when determining which implementation of the method to call at runtime. The actual types of the objects passed to the method are not considered.

For its internal event dispatch mechanism, Sofia considers the argument types as well. Using reflection, it looks up methods based on the actual types of the arguments, not the compile-time types of the reference. This achieves "multiple dispatch" for these methods, allowing the programmer to focus on their design and letting the framework manage the boilerplate glue.

© 2012–2013 Tony Allevato

Back to top