Android アプリ開発時の Intellij IDEA の設定

EAPでも厳しい感じするのですが...。

一応、ビルド→ラウンチはできるけど。

右下にアラートが出る。

開くと以下ダイアログ。


Unexpected schema version 3
java.lang.AssertionError: Unexpected schema version 3
	at com.android.dvlib.DeviceSchema.getXsdStream(DeviceSchema.java:247)
	at com.android.dvlib.DeviceSchema.getSchema(DeviceSchema.java:331)
	at com.android.sdklib.devices.DeviceParser.getParser(DeviceParser.java:503)
	at com.android.sdklib.devices.DeviceParser.parseImpl(DeviceParser.java:489)
	at com.android.sdklib.devices.DeviceParser.parse(DeviceParser.java:464)
	at com.android.sdklib.devices.DeviceManager.initUserDevices(DeviceManager.java:444)
	at com.android.sdklib.devices.DeviceManager.initDevicesLists(DeviceManager.java:287)
	at com.android.sdklib.devices.DeviceManager.getDevice(DeviceManager.java:217)
	at com.android.sdklib.internal.avd.AvdManager.parseAvdInfo(AvdManager.java:1486)
	at com.android.sdklib.internal.avd.AvdManager.buildAvdList(AvdManager.java:1382)
	at com.android.sdklib.internal.avd.AvdManager.<init>(AvdManager.java:358)
	at com.android.sdklib.internal.avd.AvdManager.getInstance(AvdManager.java:403)
	at com.android.tools.idea.avdmanager.AvdManagerConnection.initIfNecessary(AvdManagerConnection.java:170)
	at com.android.tools.idea.avdmanager.AvdManagerConnection.getAvds(AvdManagerConnection.java:277)
	at com.android.tools.idea.run.editor.DevicePicker.lambda$refreshAvds$2(DevicePicker.java:186)
	at com.intellij.openapi.application.impl.ApplicationImpl$1.run(ApplicationImpl.java:315)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

想像できるのは、android コマンドが利用できなくなったこと。2017年8月ぐらいからか。


$ ./android
*************************************************************************
The "android" command is deprecated.
For manual SDK, AVD, and project management, please use Android Studio.
For command-line tools, use tools/bin/sdkmanager and tools/bin/avdmanager
*************************************************************************

現在、最新版の 2017.3.3 なのですが。

GradleのバージョンやAndroidプラグインのバージョンなどに強く依存して頻繁な更新の影響を受けてるわけで。


This Gradle plugin requires a newer IDE able to request IDE model level 3. For Android Studio this means version 3.0+


$ adb push /projects/ExampleApp/app/build/outputs/apk/debug/app-debug.apk /data/local/tmp/com.example.app
$ adb shell pm install -r "/data/local/tmp/com.example.app"
Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]

$ adb shell pm uninstall com.example.app
Unknown failure (at android.os.Binder.execTransact(Binder.java:565))
Error while Installing APK

ある程度の対応策としては以下はあるが。


# gradle.properties
android.injected.build.model.only.versioned=3
android.injected.testOnly=false

とうとうGUI制限が入った Android Studio 3.0 Beta4 - exception think

環境だけでで振り回される外の人々。

中の人たちはそれなりの設定方法があるのだろうか。


FCM と Notification の併用をやめてバックグラウンド受信時にうれしがる

ややこしすぎな気がします。

この記事のタイトルも書いてて意味不明です。

以下のような、FCMにJSONをPOSTしてアプリで受ける場合の話です。


curl https://fcm.googleapis.com/fcm/send -X POST \
--header "Authorization: key=FirebaseConsoleのプロジェクトの設定、クラウドメッセージングのタブにあるサーバーキー" \
--Header "Content-Type: application/json" \
 -d '
 {
   "to":"/topics/all",
   "data":{
     "title":"New Notification!",
     "body":"Test"
   },
   "priority":"high"
 }'

このPOSTするJSONに含まれるデータ構造によって、アプリ側で受信する場合の処理の流れ変わって見通しが悪くなります。

きれいにまとめられています。

Firebase Notificationでアプリの状態による挙動の違いについて - Qiita

アプリがフォアグラウンドにある場合、このServiceのonMessageReceived()が呼び出されることになります。

アプリがバックグラウンドにある場合はこのonMessageReceived()は呼ばれることはなく、システム側で自動的にシステムトレイに通知を表示します。

"data"ブロックはConsoleからも含めることはできます(詳細オプションのカスタムデータで指定できる)。しかし、Consoleでは"notification"ブロックを省略することができません(メッセージ文を入力しないと送信できない)。

送信したメッセージに"data"ブロックがあっても、"notification"ブロックが存在する限り、アプリがバックグラウンドにある状態ではFirebaseMessagingServiceのonMessageReceived()は呼ばれません。

"data"ブロックの内容を受け取れないわけではありません。通知をクリックして起動したActivityのintentに含まれることになります。

"data"ブロックの内容の処理が、フォアグラウンド時に受け取ったときはFirebaseMessagingServiceで、バックグラウンドのときにはActivityでとなってしまって煩雑です。

実際に試してみると、これら書かれてることすごく良く分かります。

結局、この"notification"ブロックの特典としては、

アプリがバックグラウンド状態で受信した場合にのみ、アプリ側の実装なしで通知トレイに入る。

ということだけでしょうか、おおまかですが。

すべてのメッセージ受信時にonMessageReceived()が呼び出されれば、アプリがフォアグラウンドにあるかバックグラウンドにあるかを気にする必要がなくなります。

なるほど。

では、入れ子にしたらいいんじゃないかと。

例えば、


{
  // ...

  "notification": {
    "title" : "News!!",
    "body" : "New Product Release!!"
  },

  "data": {
    "user": "Yamada Taro",
    "age": 20
  },

  // ...
}

を以下のように入れ子にする。


{
  // ...

  "data": {
    "user": "Yamada Taro",
    "age": 20,

    "notification": {
      "title" : "News!!",
      "body" : "New Product Release!!"
    }

  },

  // ...
}

すべてサービスで受けることができます。


class  MyFirebaseMessagingService : FirebaseMessagingService() {

  override fun onMessageReceived(message: RemoteMessage) {

    // ...

    // 2層目以下はStringで送られてくるのでMap化
    message.data["notification"]?.let {
      val notification : Map<String, String> =
        Gson().fromJson(it, object : TypeToken<HashMap<String, String>>() {}.type)
      sendNotification(notification)
    }

  }

  // サンプルを参考に
  private fun sendNotification(notification: Map<String, String>) {
    val intent = Intent(this, MainActivity::class.java)
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

    val pendingIntent = PendingIntent.getActivity(this, 0, intent,
        PendingIntent.FLAG_ONE_SHOT)

    val channelId = getString(R.string.default_notification_channel_id)
    val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
    val notificationBuilder = NotificationCompat.Builder(this, channelId)
        .setSmallIcon(android.R.drawable.ic_notification_overlay)
        .setContentTitle(notification["title"])
        .setContentText(notification["body"])
        .setAutoCancel(true)
        .setSound(defaultSoundUri)
        .setContentIntent(pendingIntent)

    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    notificationManager.notify(0, notificationBuilder.build())
  }

quickstart-android/MyFirebaseMessagingService.java at master · firebase/quickstart-android

元々、Firebase てのは、敷居を下げてマーケティングなどに利用を広めようとしてるのかな。


すばやく理解する「Room x RxJava 」

いい記事があったので。

Room 🔗 RxJava – Google Developers – Medium

まずは、Room で Dao.


@Query(“SELECT * FROM Users WHERE id = :userId”)
User getUserById(String userId);

ここまでで問題なのは、

1. 同期呼び出しでブロッキング。
2. データ変更時に再度呼び出す必要がある。

ということで、RxJava を使いたくなります。

Room は RxJava2.x に対応しています。

Adding Components to your Project | Android Developers

どのように使うのか?

Maybe


@Query(“SELECT * FROM Users WHERE id = :userId”)
Maybe<User> getUserById(String userId);

1. 該当ユーザがなければ、何も返さずに complete。
2. 該当ユーザがあれば、onSuccess となり complete。
3. Maybe が complete されたあとにユーザー情報が更新されても何もしない。

Single


@Query(“SELECT * FROM Users WHERE id = :userId”)
Single<User> getUserById(String userId);

1. 該当ユーザがなければ、何も返さず onError(EmptyResultException)。
2. 該当ユーザがあれば、onSuccess。
3. Single が complete されたあとにユーザー情報が更新されても何もしない。

Flowable


@Query(“SELECT * FROM Users WHERE id = :userId”)
Flowable<User> getUserById(String userId);

1. 該当ユーザはなければ、何も返さず emit もされない。当然、onNext も onError も呼ばれない。
2. ユーザが存在すれば、onNext。
3. ユーザ情報が更新されるたびに、自動で emit されるので、UI上を最新データに更新させることが可能になる。

 

まとめ

これだけ数行でデータベース、非同期処理を簡潔明快に説明できる Room x RxJava の組み合わせ。

おまけに Observable から細分化された RxJava2.x の主役たちの使い方も理解することができます。

素晴らしいですよね。


Java から Kotlin 移行の進捗を「cloc」で確認する

これを確認したかったのです.

特定のディレクトリのファイルやコード行数の確認.

googlesamples/android-architecture at todo-mvp-rxjava

「cloc」てやつを使います.

AlDanial/cloc: cloc counts blank lines, comment lines, and physical lines of source code in many programming languages.

こんな感じで結果表示されます.


for d in ./*/ ; do (cd "$d" && echo "$d" && cloc --vcs git); done

Android プロジェクトの場合, アプリのルートディレクトリでやると,


$ pwd
/Users/maochanz/projects/PlainTemplates
$ cloc .
     674 text files.
     567 unique files.
     140 files ignored.

github.com/AlDanial/cloc v 1.74  T=10.63 s (50.4 files/s, 8239.6 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
JSON                            253              0              0          30758
XML                             223           1749            233          17707
Java                             36             38          24757          10428
Kotlin                           18            245            324            993
Bourne Again Shell                1             19             20            121
DOS Batch                         1             24              2             64
Groovy                            3              8              3             55
Prolog                            1              3              0             18
--------------------------------------------------------------------------------
SUM:                            536           2086          25339          60144
--------------------------------------------------------------------------------

となって,

build ディレクトリもカウントされるので,

src ディレクトリ以下で実行するといいでしょう.


$ pwd
/Users/maochanz/projects/PlainTemplates/app/src
$ cloc .
      77 text files.
      76 unique files.
       1 file ignored.

github.com/AlDanial/cloc v 1.74  T=0.44 s (171.9 files/s, 7442.6 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
XML                             58            177             89           1463
Kotlin                          18            245            324            993
-------------------------------------------------------------------------------
SUM:                            76            422            413           2456
-------------------------------------------------------------------------------

Kotlin による冗長なコードの削減具合もよく分かるはずです.

CLOC -- Count Lines of Code


ログ出力をクリッカブルにして該当行に素早く移動

Android Studio でのログ出力.

任意の文字列を出力する場合は,


Log.d("TEST", "テスト")

と書いておくと,

となります.

Kotlin では,


println("TEST テスト")

と書いてあげると,

となります.

以下のような文字列にすると,


println("TEST https://google.com/")

println("TEST (MainActivity.kt:100)")

と表示されて,

それぞれブラウザの該当ページや,

該当ファイルの該当行に直接クリックで遷移できるようになります.

ここで,

Exception().stackTrace[1]

を使うと, 記述した位置の該当ファイル名と行数が取得できるので,

インラインなユーテリティにします.


class LogUtils {

  companion object {

    private const val TAG : String = "TEST"

    @JvmStatic fun d(message: String) = Log.d(TAG, appendFileLine(message))
    @JvmStatic fun d(message: String, t: Throwable) = Log.d(TAG, appendFileLine(message), t)

    @Suppress("NOTHING_TO_INLINE") // ?
    private inline fun appendFileLine(message: String): String {
      val frame = Exception().stackTrace[1]
      return "$message (${frame.fileName}:${frame.lineNumber})"
    }

  }

}

あとは, 表示したいメッセージとともにお好みの場所で


LogUtils.d("テスト")

と書いておけば,

となって便利にデバッグに使えたり.