Kotlinのデフォルト値を使用した関数やコンストラクタをJavaから使用する際のtips

tl;dr

Kotlinコードで定義したデフォルト値を使用した関数,コンストラクタをJavaコードで使う事を想定する場合には@JvmOverloadsアノテーションをつける

@JvmOverloadsアノテーション

Kotlinの関数やコンストラクタの宣言で、以下のようにデフォルト値を使うと

class MyClass {
    fun myFunc(a: String = "", b: Int = 1, c: Boolean = true) {}
}

JavaからはMyClass#myFunc(String, int, boolean)のみ定義されているように見えます
変数b,cにはデフォルト値を使いつつMyClass#myFunc(String)の呼び出しを行いたいという場面では@JvmOverloadsアノテーションを使うことができます

@JvmOverloads
fun myFunc(a: String = "", b: Int = 1, c: Boolean = true) {}

Javaからは以下の4つが定義されているように見えます

  • myFunc()
  • myFunc(String)
  • myFunc(String, int)
  • myFunc(String, int, boolean)

@JvmOverloadsアノテーションをつける事で生成されるメソッドは、関数定義の後ろに宣言されているデフォルト値付き引数から順番に省略されます
今回の例ではc>b>aの順番に引数が消えた関数が生成されます

使用例: AndroidのCustomView

AndroidでCustomViewに以下のようなコンストラクタを生成する際に使うことができます

  • MyCustomView(Context)
  • MyCustomView(Context, AttributeSet)
  • MyCustomView(Context, AttributeSet, int)
class MyCustomView
@JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr)

後ろにあるデフォルト値付き引数から順番に消えるため、リフレクションを使う場合など、引数の順番が大事な場合には定義の順番に気をつける必要があります

公式ドキュメント

JvmOverloads - Kotlin Programming Language

Kotlinレポジトリのidea pluginやJ2Kのテスト実行方法メモ

Kotlin1.1.50あたりからビルドスクリプトがgradle(Gradle Kotlin Script)になった。その影響かテストの実行方法が変わっていたので、メモしておく

※以下の情報は古くなる可能性が高いのでまずはKotlin(github)のReadmeを先に読んでください

Gradleタブから実行

IntellijのGradleタブからtestを実行するタスクを見つけて実行する
だいたいはTasksverificationにあると思う

f:id:scache:20171002231145p:plain:w300

Gradleのタスクを直接実行する

Gradleなのでターミナルから直接テストタスクを実行することができる

テストの実行

例: ideaのテストを実行

./gradlew :idea:cleanTest :idea:test

テスト結果はbuildディレクトリの中に作成される

一部のテストだけ実行する

一部のテストだけ実行したいという場合がよくあると思うが、その場合は--testsオプションを使うと良い

例: ideaのorg.jetbrains.kotlin.addImportパッケージ内のテストのみ実行

./gradlew :idea:cleanTest :idea:test --tests org.jetbrains.kotlin.addImport.*

Projectツリーから実行

Intellijの設定

Readmeにも少し書いてあるが、以下のページを開き
Preferences -> Build, Execution, Deployment ->Build Tools -> Gradle -> Runner

Delegate IDE build/run actions to Gradleをチェックする。書いてある通り、チェックをするとビルドや実行のアクションをGradleに移譲することができる

f:id:scache:20171002225618p:plain:w400

テストの実行

設定が終わったらIntellijでプロジェクトツリーでテストコードがあるディレクトリを開き、右クリックをしてRun Testする

f:id:scache:20171002232833p:plain:w400

一部のテストのみ実行する

RunTestを行うと新しくRun Configurationが作られていると思うので、Edit Configurationsから編集を行う
Gradleタスクの引数で制御する形になるのでArgumentsに条件を書く

f:id:scache:20171002234650p:plain:w400

CamTwistを使ってAndroidEmulatorのカメラソースをPC画面にする

バーコード読み取り機能を作る場合などにいちいち実機でバーコードをスキャンするのが面倒臭かったのでなんとかPCに表示した画像をエミュレータのカメラソースにできないか調べてみました

CamTwist

CamTwistニコニコ生放送やTwitchなどで配信する時に、Webカメラの映像ではなく、PC画面を映像のソースとして配信するためのソフトです。エミュレータではWebカメラや内臓カメラをソースとして使えますが、同じようにCamTwistの映像も使うことができます

以下の方法はCamTwist以外にも同じ機能をもったソフトであれば有効だと思います

設定方法

CamTwist

CamTwistをダウンロードページからダウンロードしてインストールします

CamTwistの設定は、以下のようにします

  • Desktop (範囲は任意)
  • Rotation Translation(任意)
  • Zoom(任意)

f:id:scache:20170927010204p:plain:w400

エミュレータで表示した時に画像が回転した状態で表示されていたらRotationを設定してみてください

AVD

  1. エミュレータの設定画面からShow Advanced Settingsを押す
  2. Cameraの設定をwebcam0にする
  3. 設定を保存

f:id:scache:20170927010812p:plain:w400

f:id:scache:20170927014603p:plain:w200

以上で設定は終了です

エミュレータ起動

エミュレータでカメラアプリを起動して確認してみます

以下のようにCamTwistで指定した範囲の映像が表示されていればOKです

f:id:scache:20170927012641p:plain:w300

おまけ webcam0について

Macではもともと内臓カメラがついている端末がほとんどだと思いますが、エミュレータの設定では内臓カメラやCamTwistなどの映像ソースが複数ある場合でもwebcam0しか選択することができません(AndroidStudioのエミュレータ設定ではカメラがあろうがなかろうがNone, Emulated, Webcam0の3つのみ選択できるようになっているようです)。

そのため、環境によっては内臓カメラの映像が自動的に選択されてしまうかもしれません。

emulatorコマンド Commonly used optionsを見た限りではemulatorコマンドからエミュレータを起動する際にどの映像ソースを使うかを選択できそうですが、カメラ一覧を取得してもwebcam0しか表示されず任意の映像ソースを選択する方法はわかりませんでした。

FCMを使ってAndroid端末へ通知を送るスクリプト

Firebase Cloud Messaging(FCM)を使ってAndroid端末へ通知を送るスクリプトを作りました(iOSは非対応です)
スクリプト内で送られているデータはcurlを使う場合を参照してください

作成したスクリプト

ソースコードhttps://github.com/sckm/fcm-send にあがっています

インストー

あらかじめgoがインストールされている必要があります

$ go get github.com/sckm/fcm-send

使い方

以下のように、はじめにjsonのdataキーで送りたい内容をファイルに書いておきます

{
    "message": "Hello World!",
    "title": "example title"
}

その後、以下のコマンドを実行することで通知が送られます

$ token='registration token'
$ server_key='your server key'
$ fcm-send -t $token -s $server_key -p data.json

curlを使う場合

作成したスクリプトは以下のcurlコマンドと同等のリクエストを送っています

token='registration token'
server_key='your server key'
msg='{"message":"Hello World!", "title": "example title"}'

curl \
    --header "Authorization: key=${server_key}" \
    --header Content-Type:"application/json" \
    https://fcm.googleapis.com/fcm/send \
    -d "{ 
        \"to\" : \"${token}\", 
        \"data\": $msg
    }"

FirebaseCrashReportingにmappingファイルをあげるスクリプトを作った

FirebaseCrashReportingにAndroidマッピングファイルをあげる際に相対パスが使えなかったり、google-services.jsonがapp直下にない時にAppIdなどを指定するのが面倒くさかったのでgolangスクリプトを作ってみました

ソースコードhttps://github.com/sckm/fcr-upload-mapping

動作環境

インストー

go get github.com/sckm/fcr-upload-mapping

使い方

cd [Androidプロジェクトのルートパス]
fcr-upload-mapping \
    -a [サービスアカウントキーファイルのパス] \
    -c [アプリバージョンコード] \
    -p [パッケージ名] \
    -m [マッピングファイルのパス] \
    -s [google-services.jsonのパス]

google-services.jsonがapp直下にある場合は省略可能です

その他

今はgradlewがあるディレクトリでしか動かないようになっていたり、引数の説明が足りないので修正したい
Golangに慣れていないのでコードが汚い・・・

RxJava2のSingle.zipのエラー処理ではまった話

問題

RxJavaでたまにonErrorReturnItemなどでエラー処理をしていてもたまにエラーがすり抜けてきてアプリが落ちてしまうことがあった
問題となっていそうな部分を簡単に表すと以下のようになっていた

Single.zip(
  Single
  Single
)
.onErrorReturnItem

対処方法

RxJavaのissuesを調べてみると同じ問題があった
Rx2: Multiple errors in zip'ed observables throw exception

ここに書いてある対処方法は、

  • RxJavaPlugins.setErrorHandler()でエラーハンドラーを設定する(issueのコメントではsetOnErrorになっているが名前が変更になった?)
  • zipの中のSingleでonErrorReturnonErrorResumeNextなどを呼び出す

いっけん良さそうに見えるので原因がわかりづらくてはまってしまった
実際にはどうしてそうなってしまうのかまだわかっていないのでRxJavaの実装を読んでいきたい

文字列リソースに複数の値を渡す

基本的な方法

複数の値を渡す際には%1$s%2$sなどを設定する。
1や2で何番目の引数を使うかを指定する

<string name="string_res1">1番目%1$s 2番目%2$s"</string>
getString(R.string.string_res1, "A", "B")

結果: 1番目A 2番目B

渡した値の一部を使わない場合

引数で複数の値を渡すが文字列リソースで使わない場合の挙動

<string name="string_res2">2番目のみ%2$s"</string>
getString(R.string.string_res2, "A", "B")

結果: 2番目のみB

特に問題なく文字列が取得できる

必要以上に値を渡した場合

1番目の引数のみ使う文字列リソースに2つの値を渡した場合

<string name="string_res3">1番目%1$s"</string>
getString(R.string.string_res3, "A", "B")

結果: 1番目A

実行時には問題なく文字列が取得できる
ただしlintでの警告がでる
values-ja/strings.xmlを作成して日本語の場合に2つの値を使うようにしても警告がでた