Ruboto: jruby on android をつかってみる

まあだいたい [Rubotoを使ってRubyでAndroidアプリをかく](http://mojavy.com/blog/2013/03/29/ruboto/) を追体験しただけなんですが。

まず動機ですが、別にRubyでAndroid appを書きたいわけではないです。やりたいのことは以下のふたつ。

* Ruby REPLからAndroid appを操作することと
* Android appのテストをRubyで書くこと

特にテスト。文字列操作とかリスト操作はRubyのほうが得意なので、得意なことを得意な言語でやれば効率いいんじゃないかと思うわけです。

## ruboto setupがコケる

homebrewでandorid sdkをインストールしている場合、ruboto setupがコケます。

```shell-session
$ ruboto setup --target android-19

Java runtime             : Found
Java Compiler            : Found
Apache ANT               : Found
Android Package Installer: Found
Android Emulator         : Found
Android SDK Command adb  : Found
Android SDK Command dx   : Found
Platform SDK android-10  : Not found

    !!! Ruboto setup is NOT OK !!!

Android platform SDK for android-10 not found.
Would you like to download and install it? (Y/n):
```

` $ANDROID_HOME/tools `と` $ANDROID_HOME/platform-tools `をPATHに追加するといけました。


```
export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH"
```

## Hello, woldまで

```
$ ruboto gen app --package com.example.rubotoexample
$ cd rubotoexample
$ rake install
$ rake start # start the app
```

これでとりあえずアプリは立ちあがってRUBOTOのロゴがでる。

## アプリを書き換える

activity classはこんなの。けっこうゴツイ。

```ruby
require 'ruboto/base'
require 'ruboto/package'

#######################################################
#
# ruboto/activity.rb
#
# Basic activity set up.
#
#######################################################

#
# Context
#
module Ruboto
  module Context
    def start_ruboto_dialog(class_name = nil, options = nil, &block)
      if options.nil?
        if class_name.is_a?(Hash)
          options = class_name
          class_name = nil
        else
          options = {}
        end
      end

      unless options.key?(:java_class)
        java_import 'org.ruboto.RubotoDialog'
        options[:java_class] = RubotoDialog
      end

      options[:theme] = android.R.style::Theme_Dialog unless options.key?(:theme)

      start_ruboto_activity(class_name, options, &block)
    end

    def start_ruboto_activity(class_name = nil, options = nil, &block)
      if options.nil?
        if class_name.is_a?(Hash)
          options = class_name
          class_name = nil
        else
          options = {}
        end
      end

      # FIXME(uwe):  Deprecated.  Remove june 2014.
      if options[:class_name]
        puts "\nDEPRECATON: The ':class_name' option is deprecated.  Put the class name in the first argument instead."
      end

      java_class = options.delete(:java_class) || RubotoActivity
      theme = options.delete(:theme)

      # FIXME(uwe):  Remove the use of the :class_name option in june 2014
      class_name_option = options.delete(:class_name)
      class_name ||= class_name_option
      # EMXIF

      script_name = options.delete(:script)
      extras = options.delete(:extras)
      raise "Unknown options: #{options}" unless options.empty?

      if class_name.nil? && block_given?
        class_name =
            "#{java_class.name.split('::').last}_#{source_descriptor(block)[0].split('/').last.gsub(/[.-]+/, '_')}_#{source_descriptor(block)[1]}"
      end

      class_name = class_name.to_s

      if Object.const_defined?(class_name)
        Object.const_get(class_name).class_eval(&block) if block_given?
      else
        Object.const_set(class_name, Class.new(&block))
      end
      i = android.content.Intent.new
      i.setClass self, java_class.java_class
      i.putExtra(Ruboto::THEME_KEY, theme) if theme
      i.putExtra(Ruboto::CLASS_NAME_KEY, class_name) if class_name
      i.putExtra(Ruboto::SCRIPT_NAME_KEY, script_name) if script_name
      extras.each { |k, v| i.putExtra(k.to_s, v) } if extras
      startActivity i
      self
    end

    private

    def source_descriptor(proc)
      if md = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+)(?: \(lambda\))?>$/.match(proc.inspect)
        filename, line = md.captures
        return filename, line.to_i
      end
    end

  end

end

java_import 'android.content.Context'
Context.class_eval do
  include Ruboto::Context
end

#
# Basic Activity Setup
#

module Ruboto
  module Activity
    def method_missing(method, *args, &block)
      return @ruboto_java_instance.send(method, *args, &block) if @ruboto_java_instance && @ruboto_java_instance.respond_to?(method)
      super
    end
  end
end

def ruboto_configure_activity(klass)
  klass.class_eval do
    include Ruboto::Activity
  end
end

java_import 'android.app.Activity'
java_import 'org.ruboto.RubotoActivity'
ruboto_configure_activity(RubotoActivity)
```

続く！