Scala で Android アプリ開発(準備編)

備忘という名の引き継ぎ的なアレ。Mac OSX 10.7 上で開発する事を前提とする。

sbt の準備

この辺りを見ながら sbt-launch.jar をダウンロードし、/usr/local/bin 配下に sbt-launch-0.11.3.jar という名前で保存する。
このままだと使い難いので、下記の Shell Script を用意する。

$ cat /usr/local/bin/sbt
java -Dhttp.proxyHost=XXX.XXX.XXX.XXX -Dhttp.proxyPort=XXXX -Xmx512M -Dfile.encoding=UTF-8 -jar `dirname $0`/sbt-launch-0.11.3.jar "$@"
パラメータ 説明
-Dhttp.proxyHost=XXX.XXX.XXX.XXX -Dhttp.proxyPort=XXXX Proxy 設定。Proxy を経由せずに外部と http/https 接続できるのであれば不要
-Xmx512M 最大ヒープサイズ。環境に合わせて適度な値を…
-Dfile.encoding=UTF-8 これを入れないと sbt console 上で日本語が使えない

giter8 の準備

まず cs コマンドをインストールする。

$ curl https://raw.github.com/n8han/conscript/master/setup.sh | sh

~/bin 配下にコマンドがインストールされるので ~/bin に PATH を通しておく。

次に g8 コマンドをインストールする。

$ cs n8han/giter8

Android SDK の準備

Android SDK | Android Developers から Mac OS X (intel) の android-sdk_r18-macosx.zip をダウンロードし、unzip で /usr/local 配下に伸張する。すると /usr/local/android-sdk-macosx が作成されるので下記の環境変数を設定する。

$ export ANDROID_SDK_HOME=/usr/local/android-sdk-macosx

zsh を利用しているなら .zshenv に上記を追加しておく。
また /usr/local/android-sdk-macosx/tools も作成されているので PATH を通しておく。

$ export PATH=$PATH:/usr/local/android-sdk-macosx/tools

zsh を利用しているなら .zshenv に下記を追加する。

path=(/usr/local/android-sdk-macosx/tools(N) $path)

次に /usr/local/android-sdk-macosx/tools/android を実行し Android SDK Manager を起動する。

$ android sdk

Proxy 設定が必要であれば、Android SDK Manager メニューから環境設定を選択し、Proxy settings の項目を穴埋めする。
Android SDK Manager 起動後、Tools フォルダ内の Android SDK Platforms-tools と Android X.X.X (API XX) の幾つか*1にチェックを入れ、下部の Install N package ボタンを押す。
すると、/usr/local/android-sdk-macosx/platform-tools と /usr/local/android-sdk-macosx/platforms/android-NN が作成される。platform-tools には adb コマンドが入っているので PATH を通しておく。

$ export PATH=$PATH:/usr/local/android-sdk-macosx/platform-tools

Android NDK の準備

Android NDK | Android Developers から Mac OS X (intel) の android-ndk-r8-darwin-x86.tar.bz2 をダウンロードし、tar jxf で /usr/local 配下に伸張する。すると /usr/local/android-ndk-r8 が作成されるので下記の環境変数を設定する。

$ export ANDROID_NDK_ROOT=/usr/local/android-ndk-r8

PATH も通しておく。

$ export PATH=$PATH:/usr/local/android-ndk-r8

準備した環境を試す

プロジェクトを作成する。

$ cd /path/to
$ g8 jberkel/android-app -b master

Template for Android apps in Scala

package [my.android.project]: com.github.cooldaemon.HelloWorld
name [My Android Project]: Hello World
main_activity [MainActivity]:
scala_version [2.9.1]:
api_level [10]:
useProguard [true]:

Applied jberkel/android-app.g8 in hello-world

コンパイルして実機にインストールする*2

$ cd /path/to/hello-world 
$ sbt
// メッセージ省略
> compile
// メッセージ省略
> android:install-device
// メッセージ省略
>

実機でアプリを起動すると hello, world! と表示される。


実践編へ続く。

*1:実案件がターゲットとする API Level を選択する事

*2:USB デバックモードに設定した実機を Mac に接続する事を忘れないように…

Scala で Android アプリ開発(Toast 編)

Toast 表示中に Toast を表示すると問題があるらしい*1ので、次のようなラッパーオブジェクトを用意する。
package は、準備編で用意したもの。

package com.github.cooldaemon.HelloWorld

import _root_.android.content.Context
import _root_.android.widget.Toast

object AtomicToast {
  private[this] var toast: Toast = null

  def show(message: String)(implicit c: Context) = synchronized {
    if (toast != null) toast.cancel()
    toast = Toast.makeText(c, message, Toast.LENGTH_LONG)
    toast.show()
  }
}

これを Activity から使うには、次のようにする。

package com.github.cooldaemon.HelloWorld

// 省略

class MainActivity extends Activity with TypedActivity {
  implicit lazy val c: Context = this

  // 省略

  // 何らかのメソッドの中で…
    AtomicToast.show("Selected Foo")

  // 省略
}

implicit で Context を渡すのが嫌な方は適宜修正してご利用ください。

*1:先に本記事のような対策をしてしまったので、困った経験がない

Scala で Android アプリ開発(ActionBarSherlock 編)

ActionBar は使いたいけれど、ターゲットの API Level が低い場合に重宝する ActionBarSherlockScala から使う。

sbt プロジェクト設定

まずは依存するライブラリを指定する。準備編を参考に作成したディレクトリ /path/to/hello-world の直下に main.sbt を作成する。

import sbt._
import Keys._
import AndroidKeys._

libraryDependencies ++= Seq(
  "com.actionbarsherlock" % "library" % "4.0.0-SNAPSHOT" artifacts(Artifact("library", "apklib", "apklib")),
  "android" % "compatibility-v4" % "r3-SNAPSHOT"
)

次に依存するライブラリの位置を指定する。/path/to/hello-world/project/build.scala を次のように修正する。

import sbt._

import Keys._
import AndroidKeys._

object General {
  val settings = Defaults.defaultSettings ++ Seq (
    name := "Hello World",
    version := "0.1",
    versionCode := 0,
    scalaVersion := "2.9.1",
    platformName in Android := "android-15", // android-10 から android-15 に変更
    resolvers += "ActionBarSherlock snapshots" at  "http://r.jakewharton.com/maven/snapshot/" // 新規追加
  )

// 以下、変更が無いので省略

最後に依存ライブラリを取得する。

$ cd /path/to/hello-world
$ sbt
> update
// 省略
[info] Resolving com.actionbarsherlock#library;4.0.0-SNAPSHOT ...
[info] Resolving android#compatibility-v4;r3-SNAPSHOT ...
// 省略
[info] Done updating.
[success] Total time: 4 s, completed 20XX/XX/XX XX:XX:XX
> exit

API Level の修正

AndroidManifest.xml 内の タグを次のように修正する。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.drops_market.Downloader">

<!-- 変更が無いので省略 -->

  <!-- android:minSdkVersion="10" から変更 -->
  <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15"/>
</manifest>

Activity から使用する

package com.github.cooldaemon.HelloWorld

import _root_.android.app.Activity
import _root_.android.os.Bundle
import _root_.android.content.Context

// 新規に追加
import _root_.com.actionbarsherlock.app.SherlockActivity
import _root_.com.actionbarsherlock.view.{Menu, SubMenu, MenuItem}

// Activity を SherlockActivity に変更
class MainActivity extends SherlockActivity with TypedActivity {
  implicit lazy val c: Context = this
  
  override def onCreate(bundle: Bundle) {
    setTheme(R.style.Theme_Sherlock) // テーマを設定
    super.onCreate(bundle)
    setContentView(R.layout.main)

    findView(TR.textview).setText("hello, world!")
  }
  
  object MenuID extends Enumeration {
    val FOO,BAR,BAZ = Value
  }

  // 試しにメニューを追加してみる
  override def onCreateOptionsMenu(menu: Menu): Boolean = {
    menu
      .add(0, MenuID.FOO.id, Menu.NONE, "Foo")
      .setIcon(android.R.drawable.ic_menu_view)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
    
    val subMenu = menu.addSubMenu(0, MenuID.BAR.id, Menu.NONE, "Bar")

    subMenu.getItem
      .setIcon(android.R.drawable.ic_menu_more)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)

    subMenu
      .add(0, MenuID.BAZ.id, Menu.NONE, "Baz")
      .setIcon(android.R.drawable.ic_menu_add)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)

    true
  }

  override def onOptionsItemSelected(item: MenuItem): Boolean = {
    item.getItemId match {
      case id if id == MenuID.FOO.id => AtomicToast.show("Selected Foo")
      case id if id == MenuID.BAR.id => AtomicToast.show("Selected Bar")
      case id if id == MenuID.BAZ.id => AtomicToast.show("Selected Baz")
      case _ =>
    }
    true
  }
}

Toast 編で作成した AtomicToast を利用している。
テーマとして何が使えるか?SHOW_AS_ACTION_IF_ROOM って何よ?という疑問をお持ちの方は Google 先生にお尋ねください。

/path/to/hello-world/main.sbt に次のような設定していると…

javacOptions ++= Seq("-Xlint:unchecked")

scalacOptions ++= Seq("-verbose", "-unchecked", "-deprecation")

ものすごい数の ActionBarSherlock 関連の警告が出力されるが、ご愛嬌という事で。