Rejasupoem

deliver value to customers continuously or die;


iOSのSwiftとAndroidのGroovy

2014-06-05

WWDCでSwiftが発表されてTLが賑わっていますが、時を同じくして6/2〜6/4に開催されたGR8Conf Europe 2014でGroovyのAndroidサポートが発表されました。

groovy-coreに取り込まれた差分: Raw modifications to run Groovy on Android by melix · Pull Request #436 · groovy/groovy-core

さっそくAndroidアプリをGroovyで書いてみた

以前RxJavaのために書いたサンプルプロジェクトがあったので、Groovy化してみました。 と言っても、GroovyはJavaに完全な上位互換があるのでそのままでも動くので、Groovyっぽいシンタックスを使ってみました。

class ComposeMessageActivity extends Activity {

    private EditText phoneNumberEditText
    private EditText messageBodyEditText
    private TextView remainingCharactersTextView
    private Button sendMessageButton
    private ListView messageListView
    private ArrayAdapter<String> messageListAdapter

    @Override
    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_compose_message)
        findViews()
        setupViews()
        setDummyData()
    }

    private void findViews() {
        phoneNumberEditText = findViewById(R.id.phone_number_edit)
        messageBodyEditText = findViewById(R.id.message_body_edit)
        remainingCharactersTextView = findViewById(R.id.remaining_characters_text)
        sendMessageButton = findViewById(R.id.send_message_button)
        messageListView = findViewById(R.id.message_list)
    }

    private void setupViews() {
        def phoneNumberText = Events.text(phoneNumberEditText)
        def messageBodyText = Events.text(messageBodyEditText)
        def sendMessageClick = Events.click(sendMessageButton)

        messageBodyText
                .map({ text -> !text.trim().equals("") })
                .subscribe(Properties.enabledFrom(sendMessageButton))

        def maxBodyLength = getResources().getInteger(R.integer.message_body_max_length)
        messageBodyText
                .map({ text -> maxBodyLength - text.length() })
                .map({ remainingChars -> getString(R.string.remaining_characters_text, remainingChars, maxBodyLength) })
                .subscribe(Properties.textFrom(remainingCharactersTextView))

        sendMessageClick
                .flatMap({
                    Observable.combineLatest(phoneNumberText, messageBodyText,
                            { phoneNumber, messageBody -> new Message(phoneNumber, messageBody) }).take(1)})
                .subscribe({ message ->
                    if (!message.getPhoneNumber()?.trim()?.equals("")) {
                        messageBodyEditText.setText("")
                        messageListAdapter.add(message.getMessageBody())
                    } else {
                        phoneNumberEditText.requestFocus()
                    }
                })

        messageListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1)
        messageListView.setAdapter(messageListAdapter)
    }

    private void setDummyData() {
        messageListAdapter.addAll(
                (1..10).collect({
                    new Message("xxx-xxxx", "message ${it}") as String
                })
        )
    }
}

ソースコード全体はこちら: rejasupotaro/rxjava-android-example もちろんPower Assertも使えますし、safe navigationも使えます。

以下の手順でローカルに最新のgroovyをインストールすることができます。

$ git clone git@github.com:groovy/groovy-core.git
$ cd groovy-core
$ ./gradlew -PskipIndy=true install

GroovyとSwiftは似てる?

巷では

なんて言われてますが、本命はGroovyだと思います。

Apple's Swift programming language inspired by Groovy

上記のサイトから引用すると、リストやハッシュの扱いは、

// Swift
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

var occupations = [
  "Malcolm": "Caption",
  "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
var emptyMap = [:]
var emptyList = []

// Groovy
def shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

def occupations = [
  "Malcolm": "Caption",
  "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
def emptyMap = [:]
def emptyList = []

ほぼ同じ。変数宣言のvarとdefの違いはあって、SwiftのletはGroovyのfinalで代用できます。 クロージャは以下のとおりです。

// Swift
numbers.map({
  (number: Int) -> in
  let result = 3 * number
  return result
})

// Groovy
numbers.collect { int number ->
  def result = 3 * number
  return result
}

Swiftはpositional closure parametersで、Groovyは "it" というpseudo-keywordでもっと短く書けます。

// Swift
numbers.map({ 3 * $0 })
numbers.map({ it in 3 * it })

// Groovy
numbers.collect { 3 * it }

インスタンス生成のnamed paramtersも同じです。

// Swift
var message = Message(from: "John", message: "How are you?")

// Groovy
def message = new Message(from: "John", message: "How are you?")

Swiftの@lazyとGroovyの@Lazyも非常によく似ていて、どちらも初回アクセス時に一度だけ初期化されます。

// Swift
class DataManager {
  @lazy var importer = DataImporter()
}

// Groovy
class DataManager {
  @Lazy importer = new DataImporter()
}

safe navigationもあります。

// Swift
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
  println("John's residence has \(roomCount) room(s).")
}

// Groovy
def john = new Person()
def roomCount = john.residence?.numberOfRooms
if (roomCount) {
  println "John's residence has ${roomCount} room(s)."
}

所感

機能的にも(ウィジェットとかIntentとか)ハードウェア的にも(画面は変形するものとして開発するべきとか)言語的にも(SwiftとGroovyとか)両者の距離が近くなってきた気がします。 ただ、今回はAndroidがGroovyをサポートしたのではなく、GroovyがAndroidをサポートしたように、良くも悪くもオープンだというところが大きな違いだと思います。