FCM(푸시알림), GA(유저 동작 데이터)는 모두 파이어베이스 콘솔을 통해 관리할 수 있다.
매번 어떻게 발급받는지 기억이 안 나서 구글에 검색하는 게 시간이 너무 아깝다.
안드로이드 스튜디오 터미널에서 다음 코드를 입력하면 디버그 sha-1를 확인할 수 있다.
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
출력된 값 중에 SHA1 값을 파이어베이스 콘솔에서 프로젝트 생성할 때 넣어주면 된다.
프로젝트를 생성하고 google-services.json 파일을 안드로이드 프로젝트의 app 모듈 아래에 넣어줘야 한다.
<service
android:name=".FcmService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
매니페스트 파일에 위처럼 설정해준다. 이 프로젝트에서는 FcmService라는 클래스를 구현해서 알림을 전송한다.
실제 알림을 보내는 로직은 FcmService에서 수행된다.
class FcmService: FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Timber.d("[FCM] FcmService -> token: $token")
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
Timber.d("[FCM] FcmService -> data: ${message.data}")
val title = message.data["title"] ?: message.notification?.title
val body = message.data["todolist"] ?: message.notification?.body
if (message.data.isNotEmpty()) {
sendNotification(title, body)
} else {
Timber.d("[FCM] FcmService -> empty data")
}
}
private fun sendNotification(title: String?, body: String?) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(body)
.setSmallIcon(R.drawable.ic_app)
.setAutoCancel(true)
.setGroup(GROUP_KEY)
.build()
val summaryNotification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("일단")
.setSmallIcon(R.drawable.ic_app)
.setStyle(
NotificationCompat.InboxStyle()
.addLine(body)
.setSummaryText("오늘 마감 할 일: ${notificationManager.activeNotifications.size}")
)
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
notificationManager.notify(SUMMARY_ID, summaryNotification)
}
companion object {
private const val GROUP_KEY = "TODO_GROUP"
private const val CHANNEL_ID = "TODO_CHANNEL"
private const val CHANNEL_NAME = "TODO"
private const val SUMMARY_ID = 0
}
}
FCM이 동작하기 위해서는 파이어베이스로부터 fcm token을 가져와서 서버에 전달해야 한다. 현재 프로젝트에서는 카카오로그인 과정에서 fcm token을 로그인 api에 함께 보내고 있다.
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task. isSuccessful) {
Timber.d("[FCM] login -> 실패: ${task.exception}")
return@OnCompleteListener
}
val token = task.result
if (token != null) {
viewModel.getClientId(token)
}
})
token을 가져오는 것에 성공하면 상태 변수에 token을 저장해둔다. 함수 네이밍은 수정해야 할 것 같다. 저장한 token은 이후에 로그인 api에 함께 넣어서 보내면 된다.
이 프로젝트는 멀티 모듈이 적용된 프로젝트이고 공통 플러그인을 구현해서 사용하고 있기 때문에 버전 카탈로그에 GA와 관련된 의존성을 추가해주고 공통 플러그인에 GA를 추가해주었다.
with(plugins) {
apply("kotlin-parcelize")
apply("com.google.gms.google-services") // GA
}
dependencies {
// 생략
"implementation"(platform(libs.findLibrary("firebase-bom").get()))
"implementation"(libs.findLibrary("firebase-analytics").get())
}
일반적으로 gradle 스크립트에서 상단에는 plugins 블록이 존재하고 하단에는 dependencies 블록이 존재한다. 대부분의 라이브러리는 dependencies 블록에만 의존성을 추가하면 되지만 GA는 Google Play Services 플러그인을 사용해야 하므로 plugins 블록에도 추가해줘야 한다.
이런 경우에 공통 플러그인에서는 위 코드의 with 블록처럼 plugins에 추가해줘야 하는 것을 넣어서 반복적인 코드를 감소시킬 수 있다.
이제 CommonPlugins를 적용한 모듈에서는 GA를 사용하는 것이 가능하다.
실제 GA로 이벤트를 기록하기 위해서는 FirebaseAnalytics 객체를 활용해야 한다. 효과적인 이벤트 기록과 리소스 소모를 줄이기 위해 싱글톤으로 FirebaseAnalytics 객체를 관리하고 Hilt를 사용하여 의존성을 주입할 수 있다.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
@Provides
@Singleton
fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics {
return FirebaseAnalytics.getInstance(context)
}
}
이제 Hilt 모듈이 생성되었으므로 적절한 위치에 의존성을 주입해서 이벤트를 기록하는 것이 가능하다.
Hilt를 통해 뷰모델에 의존성을 주입하고 사용하는 것도 좋지만 모든 뷰모델에 의존성 주입 코드를 넣는 것이 귀찮기도 하다. 이를 해결하기 위해 AnalyticsManager라는 전역 객체를 만들어서 이벤트를 기록하는 것은 어떨까?
object AnalyticsManager {
private var firebaseAnalytics: FirebaseAnalytics? = null
fun initialize(context: Context) {
if (firebaseAnalytics == null) {
// 인터넷 권한 관련 에러는 무시해도 됨
firebaseAnalytics = FirebaseAnalytics.getInstance(context)
}
}
fun logEvent(eventName: String, params: Map<String, String> = emptyMap()) {
val bundle = Bundle().apply {
params.forEach { (key, value) ->
putString(key, value)
}
}
firebaseAnalytics?.logEvent(eventName, bundle)
}
}
초기화는 Application 클래스에서 초기화 함수를 호출해주면 된다.
@HiltAndroidApp
class PoptatoApplication: Application() {
override fun onCreate() {
super.onCreate()
val config = ClarityConfig(BuildConfig.CLARITY_ID)
// 생략
AnalyticsManager.initialize(this)
}
}
이렇게 하면 뷰모델에 FirebaseAnalytics 객체를 주입하지 않아도 이벤트를 기록하는 것이 가능하다. 실제 사용 예시는 다음과 같다.
private fun getBacklogList(categoryId: Long, page: Int, size: Int) {
AnalyticsManager.logEvent(
eventName = "get_backlog_list",
params = mapOf("button_name" to "할 일 내비게이션바 버튼", "user_action" to "백로그 전체 조회")
)
viewModelScope.launch {
getBacklogListUseCase(request = GetBacklogListRequestModel(categoryId = categoryId, page = page, size = size)).collect {
resultResponse(it, ::onSuccessGetBacklogList)
}
}
}
respect bro🔥