Learning Android with RubyMotion - Chapter 2

This is part of series of posts in which I work through the book Android Programming: The Big Nerd Ranch Guide. by Bill Phillips and Brian Hardy, and write the coding exercises with RubyMotion instead of Java. If you’re just jumping in, you may want to look over the introduction to the series to get yourself oriented.

Today we’ll look at Chapter 2, which builds on the GeoQuiz app we started in Chapter 1. This will be a fairly short post, as the chapter primarily focusses on the Model-View-Controller pattern and presents only a few new concepts related to Android programming.

In the last post, we went through all of the steps needed to create the project and get it running. At this point, I’ll assume you know how to do all that, and will jump straight into the new code.

Creating a Model

First, we create a new model for GeoQuiz. This is a pretty simple step in Ruby - we just need to add a PORO (Plain Old Ruby Object), to the app directory:

class TrueFalse
  attr_accessor :question
  attr_accessor :is_true

  def initialize(question, is_true)
    @question = question
    @is_true = is_true
  end

  def true?
    is_true == true
  end
end

(Note: Normally we could implement the true? method by using method_alias, but as of this writing, that causes a crash in RubyMotion)

Updating the View Layer

These changes happen entirely in our XML resource files, so you just need to follow the instructions in Listings 2.3 - 2.5. As before, resource files work exactly the same way in RubyMotion as they do in Java, but they’re stored in the resources subdirectory, rather than res.

Updating the Controller Layer

We now need to make several changes to our QuizActivity class. There’s not much new here in terms of Android programming; it’s mostly a matter of converting the logic from Java to Ruby. Here’s what I ended up with:

class QuizActivity < Android::App::Activity
  attr_accessor :true_button
  attr_accessor :false_button
  attr_accessor :next_button
  attr_accessor :question_view
  attr_reader :question_bank
  attr_accessor :current_index

  Toast = Android::Widget::Toast

  def onCreate(savedInstanceState)
    super
    setContentView(R::Layout::Activity_quiz)

    @question_bank = [
      TrueFalse.new(R::String::Question_oceans, true),
      TrueFalse.new(R::String::Question_mideast, false),
      TrueFalse.new(R::String::Question_africa, false),
      TrueFalse.new(R::String::Question_americas, true),
      TrueFalse.new(R::String::Question_asia, true),
    ]
    @current_index = 0

    @question_view = findViewById(R::Id::Question_text_view)
    @question_view.setText(question_bank[current_index].question)

    @true_button = findViewById(R::Id::True_button)
    @false_button = findViewById(R::Id::False_button)
    @true_button.onClickListener = @false_button.onClickListener = self
  end

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

end

At this point, you should be able to run the app and see the new question appear in the text view.

Hooking Up the Buttons

We’ve already added the “Next” button to the UI via the XML file. We now need to hook it up to our code. We can do this easily with the now-familiar findViewById method, and give it an onClickListener:

  @next_button = findViewById(R::Id::Next_button)
  @next_button.onClickListener = self

For now, we can continue to use our QuizActivity class as the click listener for all of the buttons. It complicates the logic of the onClick method somewhat, but not to the point of its being too unwieldy. As our UIs become more complex, we’ll find other ways of implementing event handlers, but for now:

def onClick(view)
  if view == next_button
    self.current_index = (current_index + 1) % question_bank.length
    question_view.setText(question_bank[current_index].question)
  else
    message_id = (view == true_button ? R::String::Incorrect_toast : R::String::Correct_toast)
    Toast.makeText(self, message_id, Toast::LENGTH_SHORT).show()
  end
end

The book correctly points out that we’ve got duplicate code to update the question, so we can extract that logic out to a method:

private

def update_question
  question_view.setText(question_bank[current_index].question)
end

And finally, we need to update the logic that checks to see if the user got the right answer, now that the questions and answers are encapsulated in our new model class:

def onClick(view)
  if view == next_button
    self.current_index = (current_index + 1) % question_bank.length
    update_question
  else
    user_clicked_true = (view == true_button)
    message_id =
      if user_clicked_true == question_bank[current_index].true?
        R::String::Correct_toast
      else
        R::String::Incorrect_toast
      end
    Toast.makeText(self, message_id, Toast::LENGTH_SHORT).show()
  end
end

And that’s it. You’re ready to launch your app and test your geography knowledge.

Adding an Icon

This section introduces you to the joys of supporting all of the different pixel densities that are available in the vast spectrum of Android devices. Fortunately, the book’s authors have done the tedious work of getting the icon files into the right sizes for you, so it’s just a matter of copying them into the right directories, and updating your XML file. Again, the only difference here is that in RubyMotion you put your resources in the resources directory, rather than res.

Challenges

The last section of this chapter suggests a few more features to add to the app, but instead of providing the code, it merely specs out the features and challenges you to come up with the implementation yourself.

In keeping with that spirit, I won’t walk through the challenges here, but they’re implemented in the Github repo for this project, so you can look there if you feel like you need some help. I would only do that as a last resort, however; you’ll learn a lot more if you can work through the challenges yourself.

Good luck!

comments powered by Disqus