2021/06/22
[Jetpack Compose]Analytics / Crashlytics / FCMを導入
概要
以下対応しました。
- Google Analyticsの導入
- Firebase Crashlyticsの導入
- Firebase Cloud Messaging(FCM)を導入して、プッシュ通知をタップすることで特定の画面を開くようにする
準備
- rootの
build.gradle
に以下の行を追加します
buildscript {
dependencies {
// 以下の2行
classpath "com.google.gms:google-services:4.3.8"
classpath "com.google.firebase:firebase-crashlytics-gradle:2.7.0"
}
}
- 次にappの
build.grade
に以下の行を追加します
plugins {
// 以下の2行
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
}
dependencies {
// 以下の4行
implementation platform("com.google.firebase:firebase-bom:28.1.0")
implementation "com.google.firebase:firebase-analytics-ktx"
implementation "com.google.firebase:firebase-crashlytics-ktx"
implementation "com.google.firebase:firebase-messaging-ktx"
}
- Firebaseコンソールからダウンロードしてきた
google-services.json
をapp
配下のディレクトリ直下に配置します
Crashlyticsのテスト
Crashlyticsは上記の作業をしただけですが、導入が完了しています。
Firebaseコンソール上で、「Crashlyticsを有効化」みたいなボタンを押さないとうまく動作しなかった気がしますが、押さなくてもできるかもしれません。
動作確認には、以下のように強制的にクラッシュするボタンを配置して実施しました。
@Composable
fun CrashlyticsTest(modifier: Modifier = Modifier) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
throw RuntimeException()
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Black,
contentColor = Color.White
),
shape = Shapes.medium,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
contentPadding = PaddingValues(16.dp),
) {
Text(text = "Force Crash!!")
}
}
}
Analyticsを有効化
以下のように、MainActivity
にFirebaseAnalytics
のインスタンスを設定しておきます。
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
class MainActivity : ComponentActivity() {
private lateinit var firebaseAnalytics: FirebaseAnalytics
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
firebaseAnalytics = Firebase.analytics
}
}
プッシュ通知の設定
プッシュ通知を受け取って、特定の画面(私のアプリの場合は記事詳細画面)を開くことができるように設定しました。
プッシュ通知の準備
以下のリソースを定義しておきます。
- プッシュ通知のステータスを表示する小さいアイコン(
drawable/ic_stat_ic_notification
として定義) - プッシュ通知アイコンの色(
@color/lightBlue_500
として定義) - プッシュ通知に利用するチャンネルID
- プッシュ通知からアクティビティを起動するために使うDeepLink用のURL
上記の3と4はstring resourceとして以下のように定義しました。
<resources>
<string name="android_scheme" translatable="false">https</string>
<string name="android_host" translatable="false">kumano-te.com</string>
<string name="default_notification_channel_id" translatable="false">com.kumanote.portfolio.fcm.default</string>
<string name="new_articles_notification_channel_id" translatable="false">com.kumanote.portfolio.fcm.new_articles</string>
</resources>
AndroidManifestの編集
以下のように編集しました
<application ...>
<!-- 以下の3つの meta-data を追加 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/lightBlue_500" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<activity
android:name=".MainActivity"
...>
<!-- 以下の intent-filter を追加して DeepLink からアプリの特定の画面を開けるようにする -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/android_scheme" android:host="@string/android_host" />
</intent-filter>
</activity>
<!-- 最後にpush通知をさばくためのサービス(中身は後述)を追加しておきます -->
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
Composable側でのDeepLinkの設定
この記事を参考にしました
@Composable
fun MainView() {
// baseとなるurlを定義
val baseUrl = "${stringResource(id = R.string.android_scheme)}://${stringResource(id = R.string.android_host)}"
val navController = rememberNavController()
Scaffold(...) {
NavHost(
navController = navController,
startDestination = "home",
) {
composable(
route = "home",
deepLinks = listOf(navDeepLink { uriPattern = baseUrl }) // この行を追加
) { backStackEntry ->
Home()
}
composable(
route = "activities/{slug}",
deepLinks = listOf(navDeepLink { uriPattern = "$baseUrl/activities/{slug}" }) // この行を追加
) { backStackEntry ->
val arguments = requireNotNull(backStackEntry.arguments)
val slug = arguments.getString("slug")
ActivityDetail(
slug = slug!!,
upPress = {
navController.navigateUp()
}
)
}
}
}
}
MyFirebaseMessagingServiceを定義
class MyFirebaseMessagingService : FirebaseMessagingService() {
companion object {
private const val TAG = "FCM"
private const val DEFAULT_NOTIFICATION_ID = 0
}
override fun onNewToken(token: String) {
Log.i(TAG, "new FCM token created: $token")
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
// 通知チャンネルが(なければ)作成しておきます。
initNotificationChannel(notificationManager = notificationManager)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d(TAG, "From: " + remoteMessage.from)
val title = remoteMessage.notification?.title
val body = remoteMessage.notification?.body
val data = remoteMessage.data
// プッシュ通知のデータからどの種類のプッシュ通知かを取得して、どのチャンネルに通知すれば良いか判定します。
val notificationType = NotificationType.values().firstOrNull {
it.type == data["type"]
} ?: NotificationType.DEFAULT
// プッシュ通知をクリックして開く画面を決めるDeepLinkを生成します。
// プッシュ通知のデータにpathというデータを含めておき、任意のURLで開けるようにしておきます。
// サーバーからはNavHostで定義してあるDeepLinkに対応しているpathのみを通知に含めるようにします。
val baseUrl = "${getString(R.string.android_scheme)}://${getString(R.string.android_host)}"
val deepLinkUri = if (data.containsKey("path")) {
val path = data["path"]?.removePrefix("/")
Uri.parse("$baseUrl/$path")
} else {
Uri.parse(baseUrl)
}
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
var notificationBuilder = if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {
val channelId = when(notificationType) {
NotificationType.NEW_ARTICLES -> getString(R.string.new_articles_notification_channel_id)
else -> getString(R.string.default_notification_channel_id)
}
NotificationCompat.Builder(applicationContext, channelId)
}
else {
NotificationCompat.Builder(applicationContext)
}
notificationBuilder = notificationBuilder
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setColor(ContextCompat.getColor(applicationContext, R.color.lightBlue_500))
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
// create intent used on tapped.
val intent = Intent(
Intent.ACTION_VIEW,
deepLinkUri,
applicationContext,
MainActivity::class.java
)
val pendingIntent: PendingIntent? = TaskStackBuilder.create(applicationContext).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
notificationBuilder.setContentIntent(pendingIntent)
initNotificationChannel(notificationManager = notificationManager)
notificationManager.notify(DEFAULT_NOTIFICATION_ID, notificationBuilder.build());
}
private fun initNotificationChannel(notificationManager: NotificationManager) {
if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {
notificationManager.createNotificationChannelIfNotExists(
channelId = getString(R.string.default_notification_channel_id),
channelName = "Default",
)
notificationManager.createNotificationChannelIfNotExists(
channelId = getString(R.string.new_articles_notification_channel_id),
channelName = "New articles",
)
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun NotificationManager.createNotificationChannelIfNotExists(
channelId: String,
channelName: String,
importance: Int = NotificationManager.IMPORTANCE_DEFAULT,
) {
var channel = this.getNotificationChannel(channelId)
if (channel == null) {
channel = NotificationChannel(
channelId,
channelName,
importance
)
this.createNotificationChannel(channel)
}
}
enum class NotificationType(
val type: String,
) {
DEFAULT(""),
NEW_ARTICLES("new-articles"),
}
プッシュ通知のテスト
以下のpythonスクリプトで通知を試しました。(問題なく動きました。)
プッシュ通知用のトークンは、onNewToken
内で出力したログからあらかじめ取得しておきます。
import firebase_admin
from firebase_admin import messaging
default_app = firebase_admin.initialize_app()
token = "YOUR-FCM-TOKEN-RETREIVED-BY-LOG-INSIDE-onNewToken"
title = "This is test..."
subtitle = "This is subtitle..."
body = "New article has been published!"
notification_data = {
"type": "new-articles",
"title": title,
"subtitle": subtitle,
"path": "/activities/jetpack-compose-shimmer-card-components"
}
message = messaging.Message(
notification=messaging.Notification(
title=title,
body=body,
),
data=notification_data,
token=token,
)
response = messaging.send(message)
print('Successfully sent message:', response)
参考資料
以上になります。
関連する記事
UI ComponentにJetpackComposeを採用したアプリを開発してみました
Jetpack Composeメインのアプリ開発を行いました。アプリ内で紹介しているAvatar Componentの開発方法を紹介します。
[Jetpack Compose]プログラミング言語のシンタックスハイライト
github.com/kbiakov/CodeView-Androidを少し改造して、Jetpack Composeでコードビューを実装してみました
[Jetpack Compose]Analytics / Crashlytics / FCMを導入
FirebaseをJetpack Composeアプリに導入して、プッシュ通知を受取り対応する画面を開くようにしました。またAnalyticsやCrashlyticsも同時に導入しました。
[Jetpack Compose]ローディングアニメーションつきカードコンポーネントの作成
rememberinfinitetransitionをうまく活用し、Jetpack Composeでローディングアニメーションつきのカードコンポーネントを作成しました。