Learning Android with RubyMotion - Chapter 1

Welcome to Chapter 1! In this installment we’ll look at the first coding project in Android Programming: The Big Nerd Ranch Guide

If you’re just jumping in, you may want to look over the introduction to this series, and pay careful attention to the “Prerequisites” section. If you don’t have those in place, you’ll run into problems right out of the gate.

(TL;DR: make sure you’ve got RubyMotion and the Android SDK, and that you can build and run the “Hello World” example on RubyMotion’s “Getting Started” page.)

If you haven’t already read through Chapter 1, you might want to do that now - the code will make a little more sense if you do.

Since this is the first project in the series, I’ll go through all the steps needed to get the code up and running. In future installments, I’ll assume you know the basics and get right into the new material.

Creating and Configuring the Application

Fortunately this is a pretty easy step in RubyMotion:

motion create --template=android GeoQuiz

Once we’ve done that, we’ll need to edit the generated Rakefile to match the settings described in the the book when running the setup wizard. Open up Rakefile in your editor, and add these lines to the Motion::Project::App.setup block:

  app.package = "net.darinwilson.android.geoquiz"
  app.main_activity = "QuizActivity"
  app.theme = "@android:style/Theme.Holo.Light"
  app.development { app.archs << 'x86' } #for genymotion support

Let’s look at each of these settings in detail:

app.package

This is a unique identifier for your app, and you should customize it to match your info.

app.main_activity

This setting is important: this specifies which Activity class will be opened when the app first launches. By default, RubyMotion uses MainActivity, and a main_activity.rb was created for you when you ran motion create. If we wanted, we could put all of our code in there, but just to keep things clear, I’m going to use the same nomenclature that the book does, so we’ll use QuizActivity.

To complete this adjustment, let’s rename the main_activity.rb file that RubyMotion gave us:

$ cd GeoQuiz
$ mv app/main_activity.rb app/quiz_activity.rb

(We’ll change the class name inside the file in a minute)

app.theme

This is optional: I just changed it so that our app will look like the screenshots in the book.

app.archs

This is also optional. If you’re going to be running the code on a physical device, you won’t need this; but if you’d like to use a reasonably fast emulator like Genymotion, you’ll need this setting.

My colleague Gant Laborde made the astute observation that this line should be wrapped in an app.development block, since we’re not going to want to do an x86 build for release. I like having the extra insurance policy in place.

Your setup block should now look something like this:

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = "GeoQuiz"
  app.package = "net.darinwilson.android.geoquiz"
  app.main_activity = "QuizActivity"
  app.theme = "@android:style/Theme.Holo.Light"
  app.development { app.archs << 'x86' } #for genymotion support
end

Creating the UI in XML

For this section, just follow the instructions in the book for creating the activity_quiz.xml and strings.xml file. XML resource files work exactly the same way in RubyMotion as they do in Java. The only difference is that you put your XML resources in the resources directory, rathen than into res.

XML? Srsly??

Yes, XML. We’re partying like it’s 1999. But there’s a good reason, so let’s talk.

Rubyists typically have an affection for XML that’s reserved only for parking tickets and root canals, and you are very likely no exception, but please bear with it for now. It is possible to create your UI in pure Ruby code, but there are some advantages to keeping the code separate so that your Activities (and later Fragments) simply control behavior and don’t try to construct UI as well (that’s that whole MVC thing people like to talk about).

And since we’re in a learning phase, let’s just go with the flow, and work with what appears to be standard practice for Android development, at least for now. As we get further along, we can look at some other ways to put together our UIs.

OK? It’ll be alright - I promise.

Integrating our Layout

Now that the XML resources have been created, we need to write some code in our Activity class to integrate the layout file. When using the IDE, as the book does, some of this code is generated automatically, but in RubyMotion we’ll need to do it ourselves.

The first thing we need to do is use our activity_quiz.xml file as the main content view for our Activity, so open up quiz_activity.rb and make the changes described in the comments below:

class QuizActivity < Android::App::Activity  # <- change "MainActivity" to "QuizActivity"

  def onCreate(savedInstanceState)
    super
    setContentView(R::Layout::Activity_quiz) # <- add this line
  end

end

A couple of things to note: first, the call to super at the beginning of onCreate is a little simpler than its Java counterpart. Java requires you to be specific about the method name and the parameters when calling a superclass implementation, but Ruby does not.

Second, the way we access R values is a little different, due to the way Java package names are handled in RubyMotion. The RubyMotion docs explain it this way:

In RubyMotion, Java packages are accessed as if they were defined as a chain of Ruby constants, where each part of the package starts with an upper-case character.

In Ruby, the java.lang.Object path can be accessed using Java::Lang::Object.

Remembering to separate the path components with :: is not too difficult, but I often find myself forgetting about the initial capital letter (Activity_quiz rather than activity_quiz), so make sure to check those carefully.

At this point, you have everything you need to try running the app. Run rake or rake device and you should see something that looks like Figure 1.11 in the book.

Wiring Up the Widgets

If you haven’t already done so, add the IDs for the buttons as described in Listing 1.6. We now need to create instance variables for the “True” and “False” buttons, and connect them to the buttons defined in XML.

We can create the instance variables the Ruby way using attr_accessor. We can then associate our XML widgets with those variables using the findViewById method just as the code in the book does - we just need to remember to access the R values the RubyMotion way:

class QuizActivity < Android::App::Activity
  attr_accessor :true_button
  attr_accessor :false_button

  def onCreate(savedInstanceState)
    super
    setContentView(R::Layout::Activity_quiz)
    @true_button = findViewById(R::Id::True_button)
    @false_button = findViewById(R::Id::False_button)
  end

end

Event Handling

To handle clicks (or, more correctly, taps) on those buttons, we have a few options.

The book recommends using anonymous inner classes, which we could certainly do, but that’s not a particularly Ruby-like idiom. Ideally, we’d like to pass in blocks or even a symbol that maps to an event handler, but the API won’t allow us to do that.

This is a pretty simple app, so let’s go for the simplest option for now:

    @true_button.setOnClickListener(self)
    @false_button.setOnClickListener(self)

Because of Ruby’s duck typing, our handler does not need to be any specific kind of class, as it does in Java. As long as the listener can respond to an onClick message, it will get the call. And since we just need the one method, it’s easy enough to add it to our Activity class, which has all the context it needs to handle the event.

  def onClick(view)
    # TBD...
  end

This may not always be the best approach, but it should work OK for now.

There’s one last bit of syntactic sugar we can sprinkle over our code. RubyMotion provides Method Shortcuts for getters, setters, and isFoo-style methods that return booleans:

As an example, the getText and setText methods of android.widget.TextView can be called using the text and text= shortcuts.

view.text # instead of view.getText
view.text = "foo" # instead of view.setText("foo")

This allows to use assignment rather than making a set call, and tightens up our code a bit:

@true_button.onClickListener = @false_button.onClickListener = self

Responding to the Clicks With Toast

We’re almost there. The last thing we’re going to do is display the correct toast in response to clicks on the True and False buttons. Add the values to the strings.xml file as described in Listing 1.12, and then we’ll be ready to implement the click listener.

The code in the book is not particularly DRY - the two click handlers are nearly identical apart from the id of the string that’s passed to the toast. Our centralized click handler can make this a little cleaner - we just need to look at the passed-in view parameter to see which button was clicked, and respond with the appropriate toast:

  def onClick(view)
    message_id = (view == true_button ?
                  R::String::Incorrect_toast :
                  R::String::Correct_toast)
    Android::Widget::Toast.makeText(self, message_id,
      Android::Widget::Toast::LENGTH_SHORT).show()
  end

Note that in RubyMotion, we have to use the full package name for the Java objects that we use. Technically, you have to do this in Java as well, but Java’s import statement allows you to pull in entire namespaces, so that classes can be referred to by their short names (which is why the code in the book can use Toast rather than android.widget.Toast).

Unfortunately, all those package names make for some hard-to-read code. We don’t have anything like Java’s import statement, but we can still create a shortcut for ourselves to improve readability. Add this near the top of the file:

  Toast = Android::Widget::Toast

Now the last line of the handler can be written like this:

  Toast.makeText(self, message_id, Toast::LENGTH_SHORT).show()

That’s a little nicer.

And that’s all the code we need for this chapter. So fire it up, and watch your toasts in action. The full code for this project is available in Github, should you need to refer to it.

Wrap Up

Whew! That was a lot explanation for such a simple app, but future installments of this series should be a little more concise now we’ve covered the basics.

I’ll be posting Chapter 2 fairly soon, so you can start diving into the material in the book when you’re ready. And don’t skip over the “For The More Curious” sections - those have a lot of useful information that helps you understand the Android platform as a whole.

comments powered by Disqus