Firebase Cloud Messaging を最新の開発環境で使おうとしたらいろいろハマったので手順をまとめてみました。
だらだら長くなったので短くまとめると、
com.google.firebase:firebase-messaging:17.0.0
と apply plugin: 'com.google.gms.google-services'
が仲が悪いapply plugin: 'com.google.gms.google-services'
を使わないようにしたgoogle-services.json
も使わないようにしたです。
Android Studio のバージョンは 3.1.2
:heavy_check_mark: Include Kotlin support
Targeting API 23 and later
:heavy_check_mark: Backward Compatibility(AppCompat)
プロジェクト削除直後の app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "net.yourdomain.fcmsample"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
の手順をトレース。Firebase Cloud Messaging(FCM) が使いたかったので "Cloud Messaging" から開始。
「Connect to Firebase」と「Add FCM to your App」をやります。
「Add FCM to your App」したあとの app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "net.yourdomain.fcmsample"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.google.firebase:firebase-messaging:11.8.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'com.google.gms.google-services'
implementation 'com.google.firebase:firebase-messaging:11.8.0'
と apply plugin: 'com.google.gms.google-services'
が追加された。後者は後で消します。
その他、ルートの build.gradle
に classpath 'com.google.gms:google-services:3.1.1'
が、app
ディレクトリに google-services.json
が追加される、 どちらも後で消します。 特に google-services.json
は、プロジェクトIDやAPI Keyなどの秘匿情報が含まれるので git にコミットしない方がよいです。
gradle sync を行うと、なんか取り消し線が付いたり、赤線が付いたり、Warning が出たりするけど、synced successfully が出て成功はしました。
に従い、必要なクラスを追加、 AndroidManifest.xml
への設定を行います。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.yourdomain.fcmsample">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<service
android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
</application>
</manifest>
MyFirebaseMessagingService.kt
package net.yourdomain.fcmsample
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage?) {
// FCMメッセージを受信したときに呼び出される
// 通知メッセージの受信
if (remoteMessage?.notification != null) {
val notification = remoteMessage.notification
val title = notification?.title ?: ""
val body = notification?.body ?: ""
android.util.Log.d("FCM-TEST", "メッセージタイプ: 通知\nタイトル: $title\n本文: $body")
}
}
}
MyFirebaseInstanceIDService.kt
package net.yourdomain.fcmsample
import android.util.Log
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.FirebaseInstanceIdService
class MyFirebaseInstanceIDService : FirebaseInstanceIdService() {
override fun onTokenRefresh() {
// トークンが更新されたときに呼び出される
val refreshedToken = FirebaseInstanceId.getInstance().token ?: ""
Log.d("FCM-TEST", "Refreshed token: $refreshedToken")
}
}
MainActivity.kt
package net.yourdomain.fcmsample
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.google.firebase.iid.FirebaseInstanceId
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val refreshedToken = FirebaseInstanceId.getInstance().token ?: ""
Log.d("FCM-TEST", "Refreshed token: $refreshedToken")
}
}
ビルドして実行します。
ここから先は実機が必要でしょうか。
Play services 入りのエミュレータでは、E/FirebaseInstanceId: Token retrieval failed: SERVICE_NOT_AVAILABLE
というエラーが出てしまいました(Google Maps API は使えるんですけど…)。
実機で動かすと、初回は MyFirebaseInstanceIDService
、次回以降は MainActivity
で FCM のトークンが Logcat に出力されます。
D/FCM-TEST: token = dRkrWdZqARg:APA91bFXU0-Z7OdL4UfyLmbRHhBq2w4OTMNncbFeIgJuqWt6EJVsRgNKUrdGGG3LZRjT9PLTGokKqb7_w4iRm1gl0Vydy77kHkkdKy0lZvNr1uNDXkaMKaiWX5n1YaxlvFtZDNYtO2Eq
な感じのやたら長い文字列です(ちなみに上の文字はフィクションです)。 Logcat に出力されたトークンをコピーしておきます。
Android アプリは起動しっぱなしにしておいてください、とりあえず。
https://console.firebase.google.com/ からプロジェクト「FcmSample」を開き、「Notification」の「使ってみる」をクリックします(なんか別途 「Cloud Messaging」もありますがトラップ?)。
さらに「最初のメッセージを送信する」をクリックして、
のように設定します。 ターゲットは「単一の端末」にして、トークンの欄にコピーしておいた文字列を貼り付けます。
で、「メッセージ送信」を押すと、ただちにメッセージが送信され、起動させておいた Androidアプリの MyFirebaseMessagingService
が受信して、次のようなログを出力します。
D/FCM-TEST: メッセージタイプ: 通知
タイトル:
本文: はろー
ので app/build.gradle
に記述されているバージョンを 17.0.0 に変更して「Sync now」します。
すると見事に Failed to resolve: com.google.firebase:firebase-core:17.0.0 というエラーになります。
小一時間ほど消費していろいろ調べた結果、最終行の apply plugin: 'com.google.gms.google-services'
が無ければエラーにならない、ことが判りました(ついでに Warning されてた "Configulation 'compile' is obsolete and ..." も出なくなります)。
apply plugin: 'com.google.gms.google-services'
は、
によると、 google-services.json
からプロジェクト情報を読み込んで、自動的にアプリに設定してくれるプラグイン、であると理解できます。
オーケー、ここで全部削除して設定は手動で行いましょう。
com.google.gms.google-services を削除した app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "net.yourdomain.fcmsample"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.google.firebase:firebase-messaging:17.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
com.google.gms.google-services を削除したルートの build.gradle
:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.41'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
google-services.json
から自動で読み込まれていた設定を手動で行うには、FirebaseApp.initializeApp(Context, FirebaseOptions)
を起動時に呼び出します。android.app.Application
クラスを拡張した MyApplication
クラスを作って、 onCreate
で行うのが一般的でしょう。MyApplication
を AndroidManifest.xml
へ追加するのを忘れずに
ちなみに FirebaseApp.initializeApp
の呼び出しがされないと、FirebaseInstanceId.getInstance()
を使用したときに、
Default FirebaseApp is not initialized in this process net.yourdomain.fcmsample. Make sure to call FirebaseApp.initializeApp(Context) first.
という例外がでます。
FCM を使うだけなら、次のように「ApplicationID」と「APIKey」を設定すればよいようです。
Firebaseの初期化を行う MyApplication.kt
:
package net.yourdomain.fcmsample
import android.app.Application
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this, FirebaseOptions.Builder()
.setApplicationId("<application_id>")
.setApiKey("<api_key>")
.build())
}
}
「ApplicationID」と「APIKey」は google-services.json
にも記述されていますが、それに頼らず Firebase のサイトからも確認できます。
https://console.firebase.google.com/ からプロジェクト「FcmSample」を開き、Project Overview → プロジェクトの設定 で表示されます。
サイトから得た「ApplicationID」と「APIKey」をソースコードにベタ貼りすると、それもよくないので、外出ししましょう。
いくつか方法がありますが、
に従ってみます。 この仕組みは、
A. システム環境変数に APP_ID と API_KEY を作成する
B. gradle を使って、 ビルド時 に、1. の値をソースコード(の BuildConfig
クラス)に埋め込む
C. 実装は BuildConfig.APP_ID
, BuildConfig.API_KEY
を使う
です。
mac の Terminal だと launchctl setenv
を使います。
環境変数名はプロジェクト名を接頭辞にして FCMSAMPLE_APP_ID
、FCMSAMPLE_API_KEY
としました。
launchctl setenv FCMSAMPLE_APP_ID 1:7618378357:android:70bef98060071fe6
launchctl setenv FCMSAMPLE_API_KEY xxxxxxxx
こんな感じで。 実行後、Android Studio を再起動します。しないと環境設定値が反映されませんでした。
BuildConfig
クラスに埋め込むapp/build.gradle
を次のように編集し、FCMSAMPLE_APP_ID
、FCMSAMPLE_API_KEY
を埋め込みます。
環境変数を埋め込んだ app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "net.yourdomain.fcmsample"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
buildConfigField("String", "APP_ID", "\"${System.env.FCMSAMPLE_APP_ID}\"")
buildConfigField("String", "API_KEY", "\"${System.env.FCMSAMPLE_API_KEY}\"")
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String", "APP_ID", "\"${System.env.FCMSAMPLE_APP_ID}\"")
buildConfigField("String", "API_KEY", "\"${System.env.FCMSAMPLE_API_KEY}\"")
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.google.firebase:firebase-messaging:17.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
buildTypes
に buildConfigField("String", "APP_ID", "\"${System.env.FCMSAMPLE_APP_ID}\"")
などを追記しています。注意点は、String
は大文字(Javaのクラス名なので)にすること、第二引数が文字列なら \"
で囲むこと、です。特に後者を忘れると、ダブルコートなしの文字列リテラルが埋め込まれてエラーになります。
BuildConfig.APP_ID
, BuildConfig.API_KEY
を使う最後に、この buildConfig を使用するように MyApplication.kt
を変更します。
BuildConfigを使用するように変更した MyApplication.kt:
package net.yourdomain.fcmsample
import android.app.Application
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this, FirebaseOptions.Builder()
.setApplicationId(BuildConfig.APP_ID)
.setApiKey(BuildConfig.API_KEY)
.build())
}
}
手順が成功していれば、BuildConfig.APP_ID
や BuildConfig.API_KEY
には、A. で設定した環境変数の値が入っているはずです。失敗していれば null
になります。
この仕組みは大抵の CIサービスでも対応しているので、リリース時には CIサービス の設定で環境変数を設定してあげれば埋め込まれるはずです。
ここまで来たらやってしまえ、ということで
も追加してみた。
諸々追加した app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
defaultConfig {
applicationId "net.yourdomain.fcmsample"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
buildConfigField("String", "APP_ID", "\"${System.env.FCMSAMPLE_APP_ID}\"")
buildConfigField("String", "API_KEY", "\"${System.env.FCMSAMPLE_API_KEY}\"")
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String", "APP_ID", "\"${System.env.FCMSAMPLE_APP_ID}\"")
buildConfigField("String", "API_KEY", "\"${System.env.FCMSAMPLE_API_KEY}\"")
}
}
dataBinding {
enabled = true
}
}
dependencies {
kapt 'com.android.databinding:compiler:3.1.2'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
def coroutines_version = '0.22.5'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
// Android Support Libraries
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
// AAC
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.persistence.room:runtime:1.1.0'
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
// FCM
implementation 'com.google.firebase:firebase-messaging:17.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
とりあえずエラーにはならなかったので、依存関係のトラブルは大丈夫みたいです。
プロジェクト全体は
に。