Generating classes for callbacks

rscottm edited this page Jan 3, 2013 · 11 revisions
Clone this wiki locally

The biggest challenge in Ruboto is getting Java code to call into Ruby code when appropriate. For example, if your UI contains a button (Java instance of android.widget.Button), you'll want to know when that button is pressed. This is done by registering an instance of a class implementing the Java interface android.widget.OnClickListener by calling the button's setOnClickListener method. JRuby actually makes this easy in the majority of cases by automatically handling single method interfaces through simple Ruby block:

button.setOnClickListener do |view|
  # Do something
end

You can also use Ruby procs:

button.on_click_listener = proc do |view|
  # Do something
end

Many interfaces only have a single method, so JRuby helps out significantly. You'll have to do a little more work to implement interfaces with multiple methods. You can do this in two ways: 1) declaring a class that implements the interface methods, 2) adding the the interface methods to an object. The following examples implements android.view.SurfaceHolder.Callback for camera apps:

class SurfaceHolderCallback
  def surfaceCreated(holder)
    # Do something
  end

  def surfaceChanged(holder, format, width, height)
    # Do something
  end

  def surfaceDestroyed(holder)
    # Do something
  end
end
@surface_holder_callbacks = Object.new
class << @surface_holder_callbacks
  def surfaceCreated(holder)
    # Do something
  end

  def surfaceChanged(holder, format, width, height)
    # Do something
  end

  def surfaceDestroyed(holder)
    # Do something
  end
end

Note: There may be times when the Java side assumes standard Java methods on the Ruby object. If so, you'll need to implement them:

  def hashCode
    hash
  end

There is one situation where we need more: when we to subclass an object and implement some of its methods in ruby (e.g., PhoneStateListener's onCallStateChanged method or View's onDraw method). Until recently, these special subclasses were generated on a development machine and then compiled into your app (e.g., RubotoView subclassed View and provided hooks to attach Ruby code to). This method still works, but it causes two problems: 1) Each time you want another class, you have to recompile it into your app; and 2) these classes have to be generated without knowing what level of Android they will be running on (new methods are being added or deprecated). The new solution is to generate the necessary classes on the device using the dexmaker project.

I'll use Ruboto IRB's demo-ruboto-irb.rb as an example. The demo mirrors some of the functionality of Ruboto IRB including an EditText that includes line numbers. To do this, we need our own subclass of EditText.


The Old Way

Here's how you'd do it before:

1) Create a subclass of EditText and compile it into your app:

ruboto gen subclass android.widget.EditText --name your.package.LineNumberEditText --method_base on

2) Import the new class in your script. ruboto_import imports the class so you can access it as a constant, LineNumberEditText, and sets up the class to handle callbacks. ruboto_import_widget imports a widget through ruboto_import and then set it up to make it easy to build the view hierarchy:

ruboto_import_widget :LineNumberEditText, "your.package"

3) Create the widget in your view hierarchy:

@edit_script = ruboto_edit_text({#:your => configuration})

4) Define the on_draw callback methods (the only one we care about):

@edit_script.initialize_ruboto_callbacks do
  def on_draw(canvas)
    # Do Something
  end
end

The New Way

Now (as of Ruboto 0.10.0 or Ruboto IRB 0.8.0) we generate the class on the device through the normal JRuby path:

class MyCustomView < android.view.View
  def onDraw(canvas)
    super
    # Do Somethng
  end
end

Examples

There are several working examples in Ruboto IRB:

  • demo-ruboto-irb.rb generates a android.widget.EditText and adds line numbers. It also creates a gesture listener to handle fling behavior.

  • demo-android-api.rb has two demos that generate a subclass of android.view.View for drawing.