Fork me on GitHub

Positronic Net Documentation

Tightening things up

If you look at the complete example, there isn’t a great deal to it, but there still is a fair amount of avoidable bookkeeping. After any modification to the list of items (we have two; we’ll have more), we need to call for the display to be refreshed; is there a way to avoid writing that after each one? Likewise, if you’ve opened a database when starting an activity, you’ll almost certainly want to close it when the activity terminates; can’t something take care of this for you? And we’ve already seen examples where the way some particular thing is expressed in code is just clumsy.

In this section, we’ll be hammering away at these issues, cutting the size of our Activity class roughly in half. It’s not a huge gain in this case, since there wasn’t all that much code to begin with, but the gains in other cases are proportionately greater. Moreover, it gives us a chance to introduce a bunch of other Positronic Net features which are interesting in their own right.

Automatically dealing with database updates

Let’s start with something fairly pervasive: updating views when the underlying data that they’re viewing changes. Right now, that’s at only two points in the code, but it could potentially be more, and we have to go through the ceremony of

    TodoItem ! Fetch{ items => adapter.resetSeq( items ) }

each time. What’s more, if there were other UI components which also displayed something about the items, they would need to be updated as well, in all those places.

What we’d like to do is arrange for the TodoItem record manager to automatically run the update on any change. One way to do this is a Watch message which is like Fetch, but runs the query on every change, not just the first time it’s called. Once we’ve taken care of some details, things are slightly more elaborate:

  override def onCreate( savedInstanceState: Bundle ) = {
    ...
    TodoItems ! AddWatcherAndFetch( this ){ items => adapter.resetSeq(items) }
    ...
    listView.setOnItemClickListener {
      new OnItemClickListener {
        override def onItemClick( parent: AdapterView[_], view: View, 
                                  posn: Int, id: Long ) = {
          TodoItems ! Delete( adapter.getItem( posn ) )
          // Fetch is no longer needed here.
        }
      }
    }
    ...
    button.setOnClickListener{
      // Fetch is no longer needed here either...
    }
  }

  override def onDestroy = {
    TodoItems ! StopWatcher( this )
    TodoDb.close
  }

What this does is that it will run the

  { items => adapter.resetSeq(items) }

change-handler the first time it’s called (that’s the Fetch part of AddWatcherAndFetch), and on every subsequent change until StopWatcher is called (that’s the AddWatcher part). As a result, the explicit Fetch operations after changes are no longer needed.

(The reason for StopWatcher is that in an application with multiple activities, the database can stay open after one of the application’s activities shuts down. In this circumstance, we obviously want the notifications to stop. And since a notifier can have multiple listeners, it needs to know which one it is being asked to shut down; the Activity uses itself as a marker for that. Any Object (in Scala terms, any AnyRef) will do; the only thing that matters is that you use the same one in AddWatcher and StopWatcher.)

Cleaning up the cleanups.

At this point, we have two instances of a common pattern: setting something up in onCreate, and tearing it down in onDelete, which can be pretty far away in the code (perforce, if onCreate is long, as it often winds up being). Code is easier to read and deal with when related sections can be physically close together. So let’s do that:

class TodoItemsActivity extends Activity with PositronicActivityHelpers {
  override def onCreate(savedInstanceState: Bundle) {
    super.onCreate( savedInstanceState )
    ...
    TodoDb.openInContext( this )
    onDestroy{ TodoDb.close }
    ...
    TodoItems ! AddWatcherAndFetch( this ){ items => adapter.resetSeq(items) }
    onDestroy{ TodoItems ! StopWatcher( this ) }
    ...
  }
  ...
}

In this example, we’ve done a couple of things. First off, we’ve added with PositronicActivityHelpers. What that does is to make a number of Positronic Net facilities available within our Activity.

The second change was to use one, in the onDestroy{ TodoDB.close }, and the similar wrapper around the StopWatcher message. What’s going on here?

What it may look like is that we’ve somehow added a new control structure to Scala, which is special-purpose and specific to Android activities. The reality is slightly more prosaic. Scala allows you to write functions, and methods, which take other functions as arguments. The onDestroy that’s being called here is a new overload of the Activity onDestroy method, which takes a piece of code as an argument (that’s the part within braces), and arranges to run it when its overload for the standard, no-argument onDestroy method is called by the framework. There are similar overloads for the other activity lifecycle stages, too, as we’ll see in a minute.

However, these pairing of lines still look a bit odd. If you’re opening a database when an activity starts, you’ll almost certainly want to close it when it shuts down. Which is a common enough pattern that there’s explicit support for it:

class TodoItemsActivity extends Activity with PositronicActivityHelpers {
  override def onCreate(savedInstanceState: Bundle) {
    super.onCreate( savedInstanceState )
    ...
    useAppFacility( TodoDb )
    ...
  }
  ...
}

That does the openInContext, and arranges the deferred close, in a single call. Which is an instance of one of the principles that the library tries to follow: if there’s something you almost certainly need to do for your app to function correctly, then the framework ought to do it for you, so you can instead concentrate on the parts of the job that are unique to your particular app.

So, what the heck is an AppFacility?

Basically, anything that obeys the same open/close protocol. For now, see the source for details, but it can be useful to declare your own to manage background machinery of your own.

On to the adapter. What we have right now is an adapter for an IndexedSeq of TodoItems, which has to be explicitly refreshed at startup, and every time the underlying sequence changes:

    val adapter = 
      new IndexedSeqAdapter(
        IndexedSeq.empty,
        itemViewResourceId = android.R.layout.simple_list_item_1 )

What might be preferable is to have an adapter for an IndexedSeq notifier, which registers itself to get the notifications. As you’ve guessed, we’ve got one:

  onCreate {
    ...
    val adapter = 
      new IndexedSeqSourceAdapter(
        this, TodoItems,
        itemViewResourceId = android.R.layout.simple_list_item_1 )
    ...
  }

This IndexedSeqSourceAdapter is a derivative of IndexedSeqAdapter, whose only added functionality is to arrange the updates (and to ask the activity to shut them down at its onDestroy, which is why the adapter takes the Activity as the first argument to its constructor).

This is pretty terse — but we can trim one last bit of fat. As we’ve already mentioned, PositronicActivityHelpers can register bits of code to be executed at any of the activity lifecycle callbacks, not just onDestroy. One of these is onCreate, so we can write:

class TodoItemsActivity extends Activity with PositronicActivityHelpers {
  onCreate {
    useAppFacility( TodoDb )
    ...
  }
  ...
}

which does the same job, without nearly so much ceremony. In particular, it keeps the necessary business of calling super.onCreate(...) out of the code, which is another example of the principle that if something must happen for an app to work right, then the framework ought to do it for you, so you can spend more time concentrating on the needs of your app, and less on the needs of the framework.

Handling user actions with less boilerplate

Another thing that’s awkward, looking at the activity code that we’ve got so far, is declaring handlers for various user actions. To show what we’ve got so far, here’s deletion:

    listView.setOnItemClickListener {
      new OnItemClickListener {
        override def onItemClick( parent: AdapterView[_], view: View, 
                                  posn: Int, id: Long ) = {
          TodoItems ! Delete( adapter.getItem( posn ) )
        }
      }
    }

(That’s the code as in the last section, without the Fetch of the revised list, which the adapter is now doing on its own.)

Now, the last section mentioned in passing that the views in the layout aren’t the standard android ListView, Button, and so forth, but enhanced versions of them. (Enhanced just by mixing in a library trait: the definitions from the library source are pretty much

class PositronicButton( context: Context, attrs: AttributeSet = null )
  extends android.widget.Button( context, attrs ) 
  with org.positronicnet.ui.PositronicHandlers

class PositronicListView( context: Context, attrs: AttributeSet = null )
  extends android.widget.ListView( context, attrs ) 
  with org.positronicnet.ui.PositronicHandlers 
  with org.positronicnet.ui.PositronicItemHandlers

where PositronicHandlers and PositronicItemHandlers declare a whole bunch of extra convenience methods. It’s time to put a few of them to use.

For starters, the anonymous OnItemClickListener in the code above is really just a wrapper around a function definition, which has to be written that way because Java doesn’t allow function definitions to stand on their own (or to be anonymous). Scala does, and the PositronicItemHandlers let us take advantage:

    listView.onItemClick{ (view, posn, id) =>
      TodoItem ! Delete( adapter.getItem( posn ))
    }

There go three lines of pure boilerplate. Similarly for the Button. Instead of

    button.setOnClickListener{
      new OnClickListener {
        override def onClick(v: View) = {
        ...
      }
    }

we can write

    button.onClick {
      ...
    }

Lastly, there’s the question of adding an item on an ‘Enter’ keypress in the textfield. There are two relevant shorthands. The one that gets access to any key event would look like this:

    val textView = findViewById( R.id.newItemText ).asInstanceOf[TextView]

    textView.onKey{ (keyCode, keyEvent) => {
      if (keyCode == KeyEvent.KEYCODE_ENTER
          && ev.getAction == KeyEvent.ACTION_DOWN) 
      {
        addItem
        return true
      }
      return false
    }

However, there’s a particularly terse shorthand for the common case of declaring that you always want an ACTION_DOWN keypress of a particular key in a particular view to be handled in a particular way:

    textView.onKey( KeyEvent.KEYCODE_ENTER ){ addItem }
Why not use implicit conversions?

People who already know Scala will have spotted that in some of these cases, we didn't really need to define traits and mix them into the widgets; we could instead have defined an "implicit conversion" from the widget to a wrapper class which supports the extra methods.

In particular, it would be possible for the `onClick` and `onListItemClick` methods we're using here can be defined in terms of the standard versions --- by wrapping their arguments in instances of the Java `Listener` classes, and feeding those to the standard API. And that even goes for the simple version of the `onKey` handler.

Things get more awkward, though, when we're trying to define extensions that need to refer to state of their own --- for instance, adding extra dispatch state, as in the per-keycode version of the `onClick` handler, which has to deal with the case of declaring handlers for, say, all of the arrow kepresses.

So, we could have two sets of extensions: one available through implicit conversions, and another, those requiring state, available only through mixin traits. Or we could have one set, with one way to get at it. For now, we're trying the latter approach.

Button is a button is a button…

Lastly, there’s the awkward Scala cast syntax mentioned in the previous section:

    val button = findViewById( R.id.addButton ).asInstanceOf[ Button ]

I mentioned there that there’s a better way. It’s time to say what it is.

One of the fringe benefits that you get with the SBT-Android plugin that we’re using is a set of TypedResources, generated from the layouts in a process analogous to the standard Android build tooling that generates R.java. The file can be found a few levels deep in a src_managed subdirectory once you’ve finished a build, and looks something like this:

package org.positronicnet.tutorial.todo
import android.app.Activity
import android.view.View

case class TypedResource[T](id: Int)
object TR {
  val newItemText = 
    TypedResource[org.positronicnet.ui.PositronicEditText](R.id.newItemText)
  val addButton = 
    TypedResource[org.positronicnet.ui.PositronicButton](R.id.addButton)
  val listItemsView = 
    TypedResource[org.positronicnet.ui.PositronicListView](R.id.listItemsView)
}
...

It also defines a TypedResource type, with a generic type parameter, and a bunch of constants of that type. So, for instance, TR.addButton is defined as a TypedResource[PositronicButton], and TR.addButton.id is the same as R.id.addButton. Lastly, it defines a utility trait:

trait TypedViewHolder {
  def findViewById( id: Int ): View
  def findView[T](tr: TypedResource[T]) = findViewById(tr.id).asInstanceOf[T]
}

which we can mix into our activity — giving it a findView method which eliminates the casts, without sacrificing type safety:

class TodoItemsActivity 
  extends Activity with PositronicActivityHelpers with ViewHolder
{
  onCreate {
    ...
    findView( TR.listItemsView ).setAdapter( adapter )

    findView( TR.listItemsView ).onItemClick{ (view, posn, id) =>
      TodoItem ! Delete( adapter.getItem( posn ))
    }

    findView( TR.addButton ).onClick {
      ...
    }
  }
}

This findView method deduces its type parameter, T, from the TypedResource it takes as an argument. So, the type of findView(TR.listItemsView) is PositronicListView because that’s the type parameter of TR.listItemsView. Similarly, the compiler knows that the result of findView(TR.addButton) is a PositronicButton. If you tried to do a

    findView( TR.adddButon ).onItemClick{ ... }

you’d get a type error at compile time, because buttons can’t do that. Not even with Positronic Net enhancements.

Putting it all together

With all the above changes, we’ve finally gotten to the version of the Activity source that you’ve already seen on the tutorial front page:

class TodoItemsActivity 
  extends Activity with PositronicActivityHelpers with TypedViewHolder
{
  onCreate {
    setContentView( R.layout.todo_items )
    useAppFacility( TodoDb )

    val adapter = 
      new IndexedSeqSourceAdapter(
        this, TodoItem,
        itemViewResourceId = android.R.layout.simple_list_item_1 )
  
    findView( TR.listItemsView ).setAdapter( adapter )

    findView( TR.listItemsView ).onItemClick{ (view, posn, id) =>
      TodoItem ! Delete( adapter.getItem( posn ))
    }

    findView( TR.addButton ).onClick { addItem }
    findView( TR.newItemText ).onKey( KeyEvent.KEYCODE_ENTER ){ addItem }

    def addItem = {
      val text = findView( TR.newItemText ).getText.toString.trim
      if (text != "") {
        TodoItem ! Save( new TodoItem( text ))
        findView( TR.newItemText ).setText( "" )
      }
    }
  }
}

It’s about half the size of the original version, but compacting code doesn’t always make it clearer. What’s more important is that the bits we’ve eliminated were boilerplate. With that stuff gone, the structure of the code that’s left, and how it relates to what the user is trying to do, is a lot easier to perceive. And that’s the real goal here.