-
Notifications
You must be signed in to change notification settings - Fork 158
Generating classes for callbacks
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.
Here's how you'd do it before:
- Create a subclass of EditText and compile it into your app:
ruboto gen subclass android.widget.EditText --name your.package.LineNumberEditText --method_base on
- 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"
- Create the widget in your view hierarchy:
@edit_script = ruboto_edit_text({#:your => configuration})
- 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
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
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.