RoomのDatabase Instanceの初期化についてのメモ
概要
Roomのcodelabで疑問に思う箇所があったのでメモ
codelabで紹介されているコード
codelabではRoomDatabaseのインスタンス生成はsingletonパターンで実装され、以下のようなコードになっている。
@Database(entities = [Word::class], version = 1) public abstract class WordRoomDatabase : RoomDatabase() { abstract fun wordDao(): WordDao companion object { @Volatile private var INSTANCE: WordRoomDatabase? = null fun getDatabase(context: Context): WordRoomDatabase { val tempInstance = INSTANCE if (tempInstance != null) return tempInstance synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, WordRoomDatabase::class.java, "Word_database").build() INSTANCE = instance return instance } } } }
@Volatile
アノテーションはKotlinのドキュメントでは次のように解説されている。
Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
したがって、codelabのコードでは変数INSTANCE
にVolatileアノテーションが付けることで複数のスレッドからアクセスをスレッドセーフにしている。
疑問
synchronizedでlockを取得する前に他のスレッドがlockを解放した場合、codelabのコードでは既にインスタンスが生成済みでもRoom.databaseBuilderが呼ばれてしまうでは?
どうするか?
以下の記事の対策3に今回のコードは近くて、synchronizedブロック内で変数instanceが初期化されているかを確認している。
シングルトンパターンの遅延初期化をスレッドセーフにするには - じゅんいち☆かとうの技術日誌
試しにPlaidのコードを見ると、synchronizedブロックの中でinstanceを再度チェックしていた。
// For Singleton instantiation @Volatile private var instance: DesignerNewsDatabase? = null fun getInstance(context: Context): DesignerNewsDatabase { return instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } }
まとめ
synchronizedブロックの中でinstanceが初期化済みかを再度チェックした方がよさそう。
Gradle Play PublisherのAndroid App Bundle Supportがもうすぐリリースされそう
Gradle Play Publisher?
Gradle Play PublisherはAPKのGoogle Play Developerコンソールへのアップロードを自動化できるGradleプラグインです。
APKのアップロード以外にもPlay Storeに掲載する各種メタデータの管理も一括で行うことができます。 アプリに関する各言語向けデスクリプションやストア画像などをバージョン管理システムで管理できるようになりますし、とても便利です。 Gradleプラグイン化されていることで、CircleCIやBitriseのようなCIサービスから実行するのも簡単なのも嬉しいポイントですね。
Android App Bundle
Android App BundleはGoogle I/O 2018で発表された新しいAndroidアプリのアップロードフォーマット(aabファイル)で、アプリのコンパイル済みのソースコードとリソースを全て含みます。APKと異なるのは、APKの生成と署名がGoogle Play側で行われる点です。その仕組上Google Play App Signingの有効化が必須となります。
Android App Bundleに対応することで、ユーザーがアプリをインストールする端末のコンフィグレーションに最適化されたAPKが生成されるようになり、不要なリソースやコードを含まないことでダウンロードするAPKサイズが小さくなります1。この新しいアプリの配布の仕組みをDynamic Deliveryと呼びます。
また、Dynamic feature modulesというモジュールをプロジェクトに追加することでアプリの機能とリソースを分離し、必要になった時に動的にダウンロードすることが可能になります。初回のダウンロードには使用頻度の高い機能のみを含めることでAPKのサイズをさらに削減できます。(2018/10現在でβ)
Android App Bundleの概要は次の動画がわかりやすくオススメです。
Gradle Play PublisherのAndroid App Bundle Support
aabファイルのアップロードにGradle Play Publisherが対応していませんでしたが2、バージョン2.0.0でついにサポートされます。
gradle-play-publisher/CHANGELOG.md at master · Triple-T/gradle-play-publisher · GitHub
READMEはすでに2.0.0のものに更新されています。./gradlew publishBundle
でaabファイルのアップロードを実行できるようですね。
CHANGELOGではto be releasedとなっているので近日中にリリースされそうです。
参考
- https://github.com/Triple-T/gradle-play-publisher
- https://developer.android.com/guide/app-bundle/
- https://developer.android.com/studio/publish/app-signing#google-play-app-signing
-
最適化されたAPKが生成されるのは5.0以上で4.4以下ではMultiple APKとしてもっとも適切なAPKが配信されます。この場合全ての言語別のリソースが含まれます。↩
-
Fastlane supplyは先に対応していました。↩
kotlin-puzzlersのnaughtyWhen問題
Twitterで#KotlinConf18jpのハッシュタグを眺めてたら次のような問題が流れてきた。
package syntax.naughtyWhen // by Ilya Gorbunov fun f(x: Boolean) { when(x) { x == true -> println("$x TRUE") x == false -> println("$x FALSE") } } f(true) f(false) // What will it print? // a) true TRUE; false FALSE // b) true TRUE; false TRUE // c) true FALSE; false FALSE // d) none of the above
この問題の正解はb
になる。Kotlin Playgroundで試してみた結果はこちら。
何故その結果になるのか疑問に思って調べるとGiHubで問題と解説が公開されていた。
kotlin-puzzlers/src/syntax/naughtyWhen at master · angryziber/kotlin-puzzlers · GitHub
when式でfalse
を渡すと実際には次のように評価されるのが理由で正解がb
になる。
when(x) { x == true -> println("$x TRUE") // 実際にはfalse == x == trueとなる。xにはfalseが入るためtrueと判定される。 }
なるほど。そして解説には
when(x)
とwhen { x == ... }
はどちらか一方を使うこと。両方を同時に使わない。
とあった。
なるほど。
参考
DiffUtilのgetChangePayloadで返したオブジェクトをどう使うか
DiffUtilのgetChangePayload
では、古いアイテムと新しいアイテムでどのフィールドが更新されたかの情報を詰めたオブジェクトを生成して返す。
どんな感じなのかは以下のサイトを参照。
- DiffUtil is a must! – ProAndroidDev
- Android DTT #12 — Optimize and Animate Your RecyclerView with DiffUtil
このreturnするPayloadがどこで使われるかちゃんとわかって無かったのだけど、onBindViewHolder(ViewHolder holder, int position, List<Object> payloads)
のpayloads
で参照できる。
RecyclerView.Adapter | Android Developers
areItemsTheSame(int, int)
がtrue
を返し、areContentsTheSame(int, int)
がfalse
を返すなら、新旧のitemエンティティ(一意に識別できるオブジェクト)自体は同じだが、そのオブジェクトの持つデータが異なる。
どのデータが新しいデータで更新されたかをgetChangePayload
でオブジェクトにして返してやり、onBindViewHolder(ViewHolder holder, int position, List<Object> payloads)
でpayloadsから更新内容を取得してViewを部分的に更新してやればゼロからViewを構築するよりコストが下がる。
この時アニメーションを掛けて更新すると良い感じになる。なるほど。
LiveDataのユニットテストを書く際に参考になる記事
LiveDataのユニットテストを書く場合にどうするか調べたのでメモ。
LiveDataのUnitTest – Kenji Abe – Medium
- こちらの記事を読むことでLiveDataのユニットテストをどう行うかついて理解できました。
- AACのcore-testingをtestImplementationに追加し忘れてテストがfailしていたので助かりました。
- LiveDataのユニットテストを行うには、
@Rule
にInstantTaskExecutorRuleを追加する必要があります。 - StackOverflowの次の記事も参考になります。
- observeForeverでLiveDataをObserverする方法を参考になりました。observeForeverでObserverを登録するとLiveDataが常にアクティブになります。
- 非同期処理の場合のテストについても参考になります。
How To Unit Test LiveData and Lifecycle Components – ProAndroidDev
- KotlinでLiveDataとlifecycleのユニットテストについて解説している記事。
lambdaMock
という関数を用意しているのがポイント。 - Lifecycleのユニットテストについても言及している。
- ProAndroidDevは参考になる記事が多いですね。
- LiveDataの性質についてまとめられた記事です。LiveDataがどのような挙動をするかがテストコードを交えてわかりやすく解説されています。
- LiveDataの性質についてだけで無く、テストコードの書き方についても参考になりました。
Mockito
Mockitoの使い方については、こちらのブログがとても参考になります。ありがたい🙏
It’s time to upgrade GCM to Firebase Cloud Messagingの意訳
FirebaseからGCM終了のお知らせとFCMへのアップグレード告知メールが来たので意訳した。ついにGCMが終了するのかー
意訳文
2016年、Google Cloud Messaging(GCM)の後継としてFirebase Cloud Messaging(FCM)をローンチしました。FCMはGCMを進化させ、通知とデータメッセージをiOS、Androidの両方に確実に送信することを可能にしています。加えて、FCMはFirebase console上でプッシュ通知を簡単に送信できる機能を提供しています。それによって、ユーザーの再獲得のためのプッシュ通知を簡単に送信できます。
FCMの改善のために注意を払い多くの時間を掛けた事で、今日私達は開発者の皆さんに来年FCMへアップグレードする必要がある事を告知する事ができました。GCMサーバとクライントAPIは非推奨になり、2019年4月11日にすみやかに削除されます。私達は、出来る限り速くFCMにアップグレードし、FCMが提供する便利な機能のアドバンテージを得る事を推奨します。
もしあなたのプロジェクトでまだGCM APIを使用しているなら、2019年4月11日までにクライアントとサーバーのコードでFCMを使用するよう更新する必要があります。今使用しているGCMトークンはFCMで引き続き動作するため既存のユーザーへプッシュ通知が送信不能にはなりません。
何をする必要があるのか?
こちらからFCMへのアップグレードプロセスを確認できます。また、こちらの動画でもマイグレーションプロセスを確認できます。
もしドキュメントで回答が得られない質問があれば、サポートページのCloud Messaging FAQsをご覧頂くかその他のサポートチャンネルを通じてご連絡ください。
あなたをFCMへ迎える事を楽しみにしています!
Firebaseチームより。
原文
In 2016, we launched Firebase Cloud Messaging (FCM), the successor to Google Cloud Messaging (GCM). As the next evolution of GCM, Firebase Cloud Messaging allows you to send notifications and data messages reliably to iOS, Android, and the Web at no cost. In addition, FCM provides you with new features like the easy-to-use notifications interface in the Firebase console, so you can easily target and test notifications to re-engage your users.
In order to devote more time and attention to improving FCM, today we’re announcing that you must upgrade to FCM in the next year. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019. We recommend you upgrade sooner rather than later so you can start taking advantage of the new features in FCM today.
If you have projects that are still using the GCM APIs, you will need to update your client and server code to use FCM before April 11, 2019. Your existing GCM tokens will continue to work with FCM so you won’t lose the ability to send to your existing users.
What do I need to do?
You’ll find a complete walkthrough of the FCM upgrade process here. You can also check out this video, which walks through the migration process.
If you have questions that aren’t answered in the deprecation documentation, take a look at the Cloud Messaging FAQs on our support page or reach out to us through one of our other support channels.
We look forward to welcoming you to FCM!
Thanks,
Firebase Team