Scalaといっしょ

ScalaでAndroidとiOSとPlayFrameworkを書いてます。

最速でandroid-sdk-pluginをAndroidStudioで使う

android-sdk-pluginプロジェクトを作る

1.コマンドからプロジェクトを作ります。
2.project/plugins.sbtとproject/build.propertiesとbuild.sbtにおまじないを書きます

android create project --target android-19 --path ./android-sdk-plugin-project --package com.example.project --activity MainActivity
cd ./android-sdk-plugin-project
mkdir ./project

cat <<END > project/plugins.sbt
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.2.10")
END

cat << END > project/build.properties
sbt.version=0.13.2-M2
END

cat << END > build.sbt
import android.Keys._

android.Plugin.androidBuild
END

これで起動する準備が出来ました。

起動します(Java編)

APILevel19のエミュレーターを起動します

コマンド作れてません、スミマセン

sbtからrunします

sbt
android:run

この時実行先端末をみつけられないと、android-sdk-pluginは以下のエラーを吐きます

[trace] Stack trace suppressed: run last android:install for the full output.
[error] (android:install) no devices connected
[error] Total time: 1 s, completed Mar 1, 2014 5:39:30 AM

起動します(Scala編)

rm src/com/example/project/MainActivity.java
cat << END > src/com/example/project/MainActivity.scala
package com.example.project

import android.app.Activity
import android.os.Bundle

class MainActivity extends Activity {
  override def onCreate(bundle: Bundle) {
    super.onCreate(bundle)
    setContentView(R.layout.main)
  }
}
END
sbt
android:run

この時も起動先端末がないとエラーが出ます

IntellijIDEA(Android Studio)化します

project/plugin.sbtにsbt-ideaを追加します(IntellijIDEA Ultimate 13の人は不要)

cat <<END >> project/plugins.sbt

resolvers += "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"

addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.7.0-SNAPSHOT")
END
sbt gen-idea

IDEAで開きます
「File」>「Open」>「android-sdk-plugin-project」

IntellijIDEA(Android Studio)の赤線を消す

こんな赤線は2点を確認して修正します。

f:id:shoscala:20140307134403p:plain

ProjectSDKを適切に設定する

f:id:shoscala:20140307134352p:plain

Moduleのandroid-sdk-plugin-projectのDependenciesのModuleSDKをAndroid API 19に設定する

f:id:shoscala:20140307134359p:plain

最後にOKを押す

android-sdk-plugin最高!!

素晴らしいプラグインなのでぜひandroid-sdk-pluginにstarしてあげましょう!

Scala on Android で play-json を使う方法

AndroidJSONを扱う場合、デフォルトのorg.jsonがありますがnullを直接扱ったりして微妙なライブラリです。
アプリ開発時のサーバーサイドとしてPlayFrameworkを使ってることもあり、使い慣れたplay-jsonを使いたいので使ってみました。
ちなみにObj->JSONこんな感じにかけます
しかし普通に

resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"  
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.2.2-RC1"

こう書いて実行すると、Proguardが頑張り過ぎた場合に以下の例外が発生します

I/dalvikvm( 1681): Could not find method scala.collection.Set.hashCode, referenced from method play.api.libs.json.JsObject.hashCode
W/dalvikvm( 1681): VFY: unable to resolve interface method 10596: Lscala/collection/Set;.hashCode ()I
D/dalvikvm( 1681): VFY: replacing opcode 0x72 at 0x0004
W/dalvikvm( 1681): Exception Ljava/lang/NullPointerException; thrown while initializing Lcom/fasterxml/jackson/databind/cfg/MapperConfigBase;
W/dalvikvm( 1681): Exception Ljava/lang/ExceptionInInitializerError; thrown while initializing Lplay/api/libs/json/JacksonJson$;
W/System.err( 1681): java.lang.ExceptionInInitializerError
W/System.err( 1681):    at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:433)
W/System.err( 1681):    at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:364)
W/System.err( 1681):    at play.api.libs.json.JacksonJson$.<init>(JsValue.scala:462)
W/System.err( 1681):    at play.api.libs.json.JacksonJson$.<clinit>(JsValue.scala)
W/System.err( 1681):    at play.api.libs.json.Json$.stringify(Json.scala:48)
W/System.err( 1681):    at play.api.libs.json.JsValue$class.toString(JsValue.scala:77)
W/System.err( 1681):    at play.api.libs.json.JsObject.toString(JsValue.scala:164)
W/System.err( 1681):    at com.kohachori.example.MessageReceivingService.com$kohachori$example$MessageReceivingService$$update$1(MessageReceivingService.scala:80)
W/System.err( 1681):    at com.kohachori.example.MessageReceivingService$$anonfun$onCreate$1$$anonfun$apply$mcV$sp$2.apply(MessageReceivingService.scala:67)
W/System.err( 1681):    at com.kohachori.example.MessageReceivingService$$anonfun$onCreate$1$$anonfun$apply$mcV$sp$2.apply(MessageReceivingService.scala:63)
W/System.err( 1681):    at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
W/System.err( 1681):    at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)
W/System.err( 1681):    at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
W/System.err( 1681):    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.pollAndExecAll(ForkJoinPool.java:1253)
W/System.err( 1681):    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1346)
W/System.err( 1681):    at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
W/System.err( 1681):    at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
W/System.err( 1681): Caused by: java.lang.NullPointerException
W/System.err( 1681):    at java.lang.Enum$1.create(Enum.java:43)
W/System.err( 1681):    at java.lang.Enum$1.create(Enum.java:35)
W/System.err( 1681):    at libcore.util.BasicLruCache.get(BasicLruCache.java:54)
W/System.err( 1681):    at java.lang.Enum.getSharedConstants(Enum.java:209)
W/System.err( 1681):    at java.lang.Class.getEnumConstants(Class.java:705)
W/System.err( 1681):    at com.fasterxml.jackson.databind.cfg.MapperConfig.collectFeatureDefaults(MapperConfig.java:73)
W/System.err( 1681):    at com.fasterxml.jackson.databind.cfg.MapperConfigBase.<clinit>(MapperConfigBase.java:28)
W/System.err( 1681):    ... 17 more

この場合はProguardが頑張りすぎないように以下の2行を追加すればplay-jsonが動くようになります

"-keep class com.fasterxml.jackson.databind.** { *; }",
"-keep interface com.fasterxml.jackson.databind.** { *; }"

Scala on Android with Dipatch

Scala on AndroidでDipatchを使う方法

Androidで普通にDipatch使おうとすると、下記の例外を投げて通信してくれません。

W/dalvikvm(18092): VFY: unable to resolve exception class 431 (Ljavax/naming/AuthenticationException;)
W/dalvikvm(18092): VFY: unable to find exception handler at addr 0x5f
W/dalvikvm(18092): VFY:  rejected Lcom/ning/http/client/providers/jdk/JDKAsyncHttpProvider;.createUrlConnection (Lcom/ning/http/client/Request;)Ljava/net/HttpURLConnection;
W/dalvikvm(18092): VFY:  rejecting opcode 0x0d at 0x005f
W/dalvikvm(18092): VFY:  rejected Lcom/ning/http/client/providers/jdk/JDKAsyncHttpProvider;.createUrlConnection (Lcom/ning/http/client/Request;)Ljava/net/HttpURLConnection;
W/dalvikvm(18092): Verifier rejected class Lcom/ning/http/client/providers/jdk/JDKAsyncHttpProvider;
W/dalvikvm(18092): Exception Ljava/lang/VerifyError; thrown while initializing Ldispatch/Http$;
W/System.err(18092): java.lang.VerifyError: com/ning/http/client/providers/jdk/JDKAsyncHttpProvider
W/System.err(18092): at com.ning.http.client.AsyncHttpClient.loadDefaultProvider(AsyncHttpClient.java:592)
W/System.err(18092): at com.ning.http.client.AsyncHttpClient.<init>(AsyncHttpClient.java:184)
W/System.err(18092): at dispatch.InternalDefaults$.client(defaults.scala:29)
W/System.err(18092): at dispatch.Http$.<init>(execution.scala:30)
W/System.err(18092): at dispatch.Http$.<clinit>(execution.scala)
W/System.err(18092): at your.application.package.ClassName$$anonfun$onCreate$2$$anonfun$apply$2.apply(MessageReceivingService.scala:61)
W/System.err(18092): at your.application.package.ClassName$$anonfun$onCreate$2$$anonfun$apply$2.apply(MessageReceivingService.scala:53)
W/System.err(18092): at scala.util.Success$$anonfun$map$1.apply(Try.scala:206)
W/System.err(18092): at scala.util.Try$.apply(Try.scala:161)
W/System.err(18092): at scala.util.Success.map(Try.scala:206)
W/System.err(18092): at scala.concurrent.Future$$anonfun$map$1.apply(Future.scala:235)
W/System.err(18092): at scala.concurrent.Future$$anonfun$map$1.apply(Future.scala:235)
W/System.err(18092): at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
W/System.err(18092): at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)
W/System.err(18092): at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
W/System.err(18092): at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.pollAndExecAll(ForkJoinPool.java:1253)
W/System.err(18092): at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1346)
W/System.err(18092): at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
W/System.err(18092): at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

対処法は、下記の処理を通信前に実行することです。なぜ動くようになるのか謎ですが、AndroidでDispatchが使えるようになるのは幸せなことなのでこれで良いんです。理由がわかる方は教えて下さい。

import com.ning.http.client.AsyncHttpClientConfig
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider
new NettyAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build)