teaching machines

CS 491 Lecture 5 – More Ketchup

September 20, 2011 by . Filed under cs491 mobile, fall 2011, lectures.

Agenda

Splitting layout evenly

Last lecture, someone asked how to split the layout up independent of the widget’s content. The way to do this is to have the containing view group fill its parent, set each child widget to have a span of 0, and then give it a proportional weight. The only change from what we did last time is that we used wrap_content rather than a value of 0.

Generating and advancing words

Ultimately, we decided that we want an editable database of words and categories. That’s a big job, so let’s just stub in a solution for now.

Now, how do we want to go about advancing to the next word? I believe we said just a screen tab would suffice. Let’s attach a click handler to the word TextView.

Adding a timer

Before we go on, tell me everything you know about threading.

We want to optionally show a timer in our Catchphrase game. How do we go about updating it each second? Your first thought might be to check out Android’s Thread class, which looks pretty much like standard Java’s. Let’s create a little tester activity to experiment with. We’ll add a simple text view and fill it with a Random value every second:

public class BadUIUpdater extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create a big text label
    final TextView textView = new TextView(this);
    textView.setText("SOME TEXT");
    textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 64.0f);
    setContentView(textView);

    // Give label a random value every second
    new Thread() {
      @Override
      public void run() {
        while (true) {
          textView.setText("" + new Random().nextInt());
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            break;
          }
        }
      }
    }.start();
  }
}

When we run this we get a CalledFromWrongThreadException. The rule we’re violating is that UI components can only be twiddled with on the main thread, the one on which onCreate and all our Activity and View callbacks execute. We, however, just made a new thread and tried to alter the TextView on it. That’s bad. Why? Well, the UI thread and our threads may both mess with a widget at the same time, undermining each other.

What we need is a facility to drop our UI twiddling code into the UI thread. We can do that easily with a Handler object. A Handler encapsulates a queue of actions that need to be performed on the UI thread when there’s time. We make one like so:

Handler handler = new Handler() {
  @Override
  public void handleMessage(Message message) {
    textView.setText("" + new Random().nextInt(10));
    sendEmptyMessageDelayed(0, 1000);
  }
};

The method handleMessage is overridden to process one message on the queue. Here we don’t really care what the message is, we just update the TextView and schedule the next message for 1000 milliseconds later. We trigger the first update with:

handler.sendEmptyMessage(0);

Slick. All the threading and synchronization is hidden from us.

Okay, now that we’ve gotten that out of the way, let me tell you that there’s a class that already performs the task of counting down and updating widgets on the UI thread. It’s called CountDownTimer:

new CountDownTimer(30000, 1000) {
  @Override
  public void onTick(long millisUntilFinished) {
    textView.setText("" + new Random().nextInt(10));
  }

  @Override
  public void onFinish() {}
}.start();

It uses a Handler internally.

Dialogs

Android provides a dialog builder class that is very, very flexible. Here’s a dialog to show a simple message:

public class DialogTest extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    dialog.setMessage("We need to talk.");
    dialog.show();
  }
}

It’s important to note that dialog.show() is non-blocking. Control comes back immediately and the rest of our code is executed. Let’s prove it by inserting this line after showing the dialog:

Toast.makeText(this, "dialog.show() is non-blocking!", Toast.LENGTH_LONG).show();

If you want code to be executed based on a user’s response, we can add some buttons:

dialog.setPositiveButton("Okay", new OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog,
                      int which) {
    Toast.makeText(DialogTest.this, "hit okay", Toast.LENGTH_SHORT).show();
  }
});
    
dialog.setNegativeButton("Cancel", new OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog,
                      int which) {
    dialog.cancel();
  }
});

Our callbacks are executed on the UI thread, so we can manipulate widgets. However, we have to make sure that any long operation gets pushed off into its own thread. Otherwise the user will get an ANR dialog.

Let’s jerry-rig this to award points to the two teams.

[code-code-code]

Now for the greatest part about Android dialogs. They have a setView method which can take any View instance and it’ll incorporate it into the dialog. That’s very, very nice. And we can use it to create a dialog which asks the user for some team names! Let’s use a TableLayout for this one and let’s design it in XML.

[code-code-code]

When we define views and layouts in XML, we have to have a facility to bring them into code. We’ve said that findViewById does that. However, findViewById starts looking in the hierarchy attached to the activity using setContentView. In the case of our team name dialog view, it’s not part of that hierarchy. For these isolated views, we must load or inflate them ourselves with something like:

LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.team_names, null);

It’s said to be inflated because Android stores our layouts and other XML data in a compact binary form.

TODO

Haiku

what do robots read?
XML, unindented
and they love SQLs