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
も使わないようにした - Firebase のプロジェクト設定は環境変数から埋め込むようにした
です。
1. とりあえず Android Studio で新規プロジェクトを作る
-
Android Studio のバージョンは 3.1.2
-
Include Kotlin support
-
Targeting API 23 and later
-
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'
}
2. プロジェクトに Firebase を追加する
の手順をトレース。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 が出て成功はしました。
3. FCM をとりあえず使うところまで実装する
に従い、必要なクラスを追加、 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 に出力されたトークンをコピーしておきます。
4. メッセージを送って、受信してみる
Android アプリは起動しっぱなしにしておいてください、とりあえず。
https://console.firebase.google.com/ からプロジェクト「FcmSample」を開き、「Notification」の「使ってみる」をクリックします(なんか別途 「Cloud Messaging」もありますがトラップ?)。
さらに「最初のメッセージを送信する」をクリックして、
のように設定します。
ターゲットは「単一の端末」にして、トークンの欄にコピーしておいた文字列を貼り付けます。
で、「メッセージ送信」を押すと、ただちにメッセージが送信され、起動させておいた Androidアプリの MyFirebaseMessagingService
が受信して、次のようなログを出力します。
D/FCM-TEST: メッセージタイプ: 通知
タイトル:
本文: はろー
5. Firebase のライブラリを最新にする
- で追加された Firebase のライブラリのバージョンは 11.8.0 でしたが、2018年6月現在の最新は 17.0.0 です。
ので 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」をソースコードにベタ貼りすると、それもよくないので、外出ししましょう。
いくつか方法がありますが、
- Gradle tips and recipes|Android Developers
- Environment Variables - Visual Studio App Center | Microsoft Docs
- AndroidStudioに環境変数を渡す - Qiita
に従ってみます。
この仕組みは、
A. システム環境変数に APP_ID と API_KEY を作成する
B. gradle を使って、 ビルド時 に、1. の値をソースコード(の BuildConfig
クラス)に埋め込む
C. 実装は BuildConfig.APP_ID
, BuildConfig.API_KEY
を使う
です。
A. システム環境変数に APP_ID と 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 AIzaSyDIUooI-LGeKAAaT6lf8sQEAemJcGx7R4s
こんな感じで。
実行後、Android Studio を再起動します。しないと環境設定値が反映されませんでした。
B. gradle を使って、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のクラス名なので)にすること、第二引数が文字列なら \"
で囲むこと、です。特に後者を忘れると、ダブルコートなしの文字列リテラルが埋め込まれてエラーになります。
C. 実装は 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サービス の設定で環境変数を設定してあげれば埋め込まれるはずです。
DataBinding、kapt, coroutine, AAC, Support Libs を追加する
ここまで来たらやってしまえ、ということで
- DataBinding
- kapt
- Kotlin coroutine - 0.22.5
- Android Architecture Components(AAC) - 1.1.0 〜 1.1.1
- Android Support Library - 27.1.1
も追加してみた。
諸々追加した 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'
}
とりあえずエラーにはならなかったので、依存関係のトラブルは大丈夫みたいです。
最後に
プロジェクト全体は
に。