In Parts 1 and 2 of this series we examined different methods of creating a reactive application on Android, where events (either UI events originating from the application user, or background events generated by the system) trigger certain actions. We examined using an event bus and Observables in the context of a demo application, a simple file downloader. In this post I will examine how you construct an Android application using an actor system.
Actor model of managing concurrency comes from Erlang. Akka provides Actor implementation in Java and Scala. I will omit detailed descriptions of actors and Akka, but if you would like an introduction to the topic I’ve listed some useful presentations below.
WARNING: The method described below is only for demonstration purposes, and should not be used in production. Akka framework has been optimized for use on server infrastructure, and is not a good fit on mobile for various reasons. That said, the actor model is in my opinion a good fit for structuring mobile applications, and assuming it gets support from a library it can be a significant improvement to the way we write mobile applications in the new world of multicore pocket-sized devices.
The code for the demo application is available on github. This application is written in Scala to make interaction with Akka less verbose, but if you are unfamiliar with the language don’t let that turn you away. Don’t focus too much on the syntax, just follow the concepts. If you want to try to build the application, the README file should provide the instructions to run it on your device or emulator.
All the code we need is packed into DownloadActivity.scala and Downloader.scala. There are a couple other files in the package that assist us. DownloadApplication.scala contains the custom application that initializes the actor system on start. AndroidDispatcher.scala is a custom Akka dispatcher that allows specified Actors to always run on the UI thread. The configuration for the akka system is in res/raw/akka, and describes the dispatchers. These are not optimal settings, only enough for the system to work.
Let’s now dig into DownloadActivity. As before, its just a container for the DownloadFragment. The only thing to notice is that the Activity extends TypedActivity, which is generated by the scala build tool plugin to create a scala-friendly development environment. The TypedActivity gives us easy access to typed views. When writing a Java Android app, you have to cast anything returned by Activity.findViewById
from View to the type you want to work with (ImageView, Button, TextView, etc). But that information is available in the XML resources! So instead of losing it, the sbt plugin creates a mapping, and gives you the view of the correct type when you request it.
DownloadFragment houses all of our UI. The first method in the class may seem a bit cryptic if you haven’t worked with scala:
// Implicit conversion that lets us use anonymous functions as onClickListeners
implicit def onClickListener(f: (View => Unit)): View.OnClickListener = {
new View.OnClickListener() {
override def onClick(v: View) {
f(v)
}
}
}
As the comment says, this sets up an implicit conversion allowing us to use lambda functions directly on buttons as click listener actions. The fact that it is implicit means the compiler will look for it whenever it needs a View.OnClickListener
, but instead is given an anonymous function. The parameter to the method is a higher order function from View to Unit (scala’s equivalent of void, ie no return value). The method then returns a new instance of the View.OnClickListener
with onClick
method calling the function it was passed in. Jumping further down, it allows us to use simple functions in setOnClickListener calls:
rootView.findView(TR.downloadButton).setOnClickListener((v: View) => {
downloadButtonActor.tell(ClickEvent(v.asInstanceOf[Button]), fragmentCompanion)
})
rootView.findView(TR.resetButton).setOnClickListener((v: View) => {
resetButtonActor.tell(ClickEvent(v.asInstanceOf[Button]), fragmentCompanion)
})
We’ll come back to the actions attached to the buttons in a little bit.
Right at initialization DownloadFragment creates a few actors it will need:
val system = getApplication().asInstanceOf[DownloadApplication].actorSystem
val downloadButtonActor = system.actorOf(Props[DownloadButton]()
.withDispatcher("akka.actor.main-thread"))
val resetButtonActor = system.actorOf(Props[ResetButton]
.withDispatcher("akka.actor.main-thread"))
val fragmentCompanion = system.actorOf(Props(new DownloadFragmentActor())
.withDispatcher("akka.actor.main-thread"))
val downloader = system.actorOf(Props(new Downloader()), "downloader")
system.actorOf
gives an actor of type supplied in Props configuration. For some actors that do operations on the UI thread we also call Props.withDispatcher
giving our custom dispatcher that pins the actor to the UI thread. Other actors (ex: the Downloader) should run on another thread, and not block the UI thread.
I also decided to create a Fragment companion actor to handle the events, defined as follows:
class DownloadFragmentActor extends Actor {
def receive = {
case event: DownloadProgressEvent => updateProgress(event)
case DownloadFinishedEvent => {
// Nothing that we need to do
}
case TriggerStartDownloadEvent => {
downloader ! StartDownload(findView(TR.urlEditText).getText.toString)
}
case TriggerPauseDownloadEvent => downloader ! PauseEvent
case TriggerResumeDownloadEvent => downloader ! ResumeEvent
case TriggerResetDownloadEvent => {
updateProgress(DownloadProgressEvent(0, 0))
downloadButtonActor ! ResetButtonState(findView(TR.downloadButton))
downloader ! ResetEvent
}
}
def updateProgress(progress: DownloadProgressEvent) {
// View elements are wrapped in an Option, so foreach will
// apply only to ones that are not None/null.
progressBar foreach (_.setProgress(progress.getProgress))
downloadProgressTextView foreach (_.setText(String.format("%s / %s",
progress.getLoadedBytes, progress.getTotalBytes)))
}
}
Actors define the receive function which gets called when a message arrives. In it we match the message type using scala’s pattern matching – switch statement on steroids. Thanks to this ability we can define the messages by case classes or case objects (in this case, singleton instantiations where a case class would be without parameters). I won’t go into the details of specific message handling just yet, let’s look through the rest of the application:
class DownloadButton extends Actor with ActorLogging {
def receive = download
val download: Receive = {
case ClickEvent(v: Button) => {
sender ! TriggerStartDownloadEvent
v.setText("Pause")
context.become(pause)
}
case RestoreButtonState(v: Button) => v.setText("Download")
case ResetButtonState(v: Button) => performReset(v)
}
val pause: Receive = {
case ClickEvent(v: Button) => {
sender ! TriggerPauseDownloadEvent
v.setText("Resume")
context.become(resume)
}
case RestoreButtonState(v: Button) => v.setText("Pause")
case ResetButtonState(v: Button) => performReset(v)
}
val resume: Receive = {
case ClickEvent(v: Button) => {
sender ! TriggerResumeDownloadEvent
v.setText("Pause")
context.become(pause)
}
case RestoreButtonState(v: Button) => v.setText("Resume")
case ResetButtonState(v: Button) => performReset(v)
}
def performReset(v: Button) {
v.setText("Download")
context.become(download)
}
}
DownloadButton is an actor companion for the UI button view. It captures the button state by using context.become()
and handling the restore events (by setting appropriate text upon rotation).
The download logic has been created into an actor in Downloader.scala. (Side note, Scala doesn’t have the same restrictions as Java with regards to placement of classes in files, so multiple classes are frequently found in one file. This sometimes makes them hard to find, so make sure not to abuse this feature). The Downloader actor has two states – awaiting download, and downloading. When awaiting download, it accepts a start download message that places it into the downloading state. In it, a Future is kicked off to perform the download on a separate thread, while still accepting messages to control the process (pause, resume, reset). As before the download here represents a process you would like to happen in the background detached from your application UI, since you never block the UI thread.
Let’s now trace through a usecase and see the message flow in the actor system that’s created.
When you click on the Download button, a ClickEvent
is sent to the DownloadButton actor. Since we’re coming from outside the akka system we provide the sender of the message to be the DownloadFragmentActor companion:
downloadButtonActor.tell(ClickEvent(v.asInstanceOf[Button]), fragmentCompanion)
(Side note, infix operator (dot) is optional in Scala, and exclamation point is a synonym for the tell method, so above can be written as downloadButtonActor ! (ClickEvent(v.asInstanceOf[Button]), fragmentCompanion)
).
Next, the event is handled in the download method on the DownloadButton
. That generates a TriggerStartDownloadEvent
that gets sent to the sender, which we defined to be the DownloadFragmentActor
. There a StartDownload
message gets sent to the downloader
, with the URL extracted from the EditText view. In the downloader, the download is triggered, and the Actor switches into the awaitingDownload
functionality to properly handle pause/resume/reset events. I invite you to do the same exercise with other messages to see how they propagate through the hierarchy.
I hope with this brief introduction you can see advantages to using an actor model for designing your application. To summarize the actor system use on Android:
- Abstractions over state and concurrency handling, pushing the details into the framework and out of your code.
- Limited support at this time in the Android ecosystem.
This post concludes the series, but the topic is certainly far from complete. Are there any methods you like to use to create Android applications in a reactive fashion? If so, please share!
Resources: