Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 : Firebase Cloud Messageing 수신

Billcorea 2022. 4. 12. 22:41
반응형

앞에서 python 코드를 이용해서 random 숫자를 만들고 FCM 전송하는 코드를 구현해 보았다면, 이번에 그걸 이용해서 수신하는 앱을 하나 만들어 볼 차례다.

 

https://billcorea.tistory.com/179

 

자작 앱 설명서 : 로또에 관심 있으세요?

https://play.google.com/store/apps/details?id=com.billcoreatech.getLotto Get Lotto 로또 번호를 드립니다. - Google Play 앱 매주 요청한 요일에 로또 번호를 무작위로 보내 드립니다. play.google.com 앱을..

billcorea.tistory.com

 

코드 구현은 kotlin으로 해 보았다.  이제 걸음마 단계이기 때문에 코드가 조금 길어질 수 도 있지만, 아직은 준비 중인 단계이기 때문에...

 

먼저 FCM을 수신하기 위해서는 firebase와 연동을 위한 gradle 구성이 필요하다.

 

import java.text.SimpleDateFormat

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
    id 'com.google.firebase.crashlytics'
    id 'kotlin-android-extensions'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.bi.......tto"
        minSdk 28
        targetSdk 32
        versionCode 10
        versionName "0.1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        debug {
            buildConfigField "Boolean", "DEBUG_MODE", "true"
        }
        release {
            buildConfigField "Boolean", "DEBUG_MODE", "false"
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    // jetPack compose add
    buildFeatures {
        viewBinding true
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    def archiveBuildType = ["release"]
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            if (variant.buildType.name in archiveBuildType) {
                def df = new SimpleDateFormat("yyyyMMdd")
                df.setTimeZone(TimeZone.getDefault())
                if (variant.versionName != null) {
                    String name = "GetLotto645_${df.format(new Date())}_${defaultConfig.versionCode}_${variant.versionName}.apk"
                    output.outputFileName = name
                }
            }
        }
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'com.google.android.play:core:1.10.3'
    
    // 파이어 베이스 연동을 위한 설정...
    implementation platform('com.google.firebase:firebase-bom:29.2.1')
    // 메시징
    implementation 'com.google.firebase:firebase-messaging:23.0.2'
    // 인증처리
    implementation 'com.google.firebase:firebase-auth-ktx'
    implementation 'com.google.android.gms:play-services-auth:20.1.0'
    // realtime database
    implementation 'com.google.firebase:firebase-database-ktx'
    // crashlytics 
    implementation 'com.google.firebase:firebase-crashlytics-ktx'
    implementation 'com.google.firebase:firebase-analytics-ktx'
    // safetynet 앱 인증
    implementation 'com.google.firebase:firebase-appcheck-safetynet:16.0.0-beta05'
    implementation 'com.google.firebase:firebase-appcheck-debug:16.0.0-beta05'
    
    implementation 'androidx.preference:preference-ktx:1.2.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation "androidx.core:core-ktx:1.7.0"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.google.android.gms:play-services-ads:20.6.0'

    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
    implementation 'com.google.zxing:core:3.4.1'

}

 

다음은 manifest에 intenet 활용을 위한 permission 선언이 필요하고.

 

<uses-permission android:name="android.permission.INTERNET" />

그리고 service을 등록해야 한다. FCM 수신을 위한 리시버...

<service
    android:name="com.billcoreatech.getLotto.utils.FcmReceiveService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

 

다음은 kotlin으로 구현한 service 코드 (사실 개발자 문서의 내용을 그대로 옮겨 왔다고 해도 과언은 아닐 것이다.)

package com.b...................to.utils

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.media.RingtoneManager
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.billcoreatech.getLotto.MainActivity
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import getLotto.R

class FcmReceiveService : FirebaseMessagingService() {

    var TAG:String = "FcmReceiveService"

    override fun onMessageReceived(remoteMessage: RemoteMessage) {

        // TODO(developer): Handle FCM messages here.
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.e(TAG, "From: " + remoteMessage.from)

        // Check if message contains a data payload.
        if (remoteMessage.data.size > 0) {
            Log.e(TAG, "Message data payload: " + remoteMessage.data)
            sendNotification(remoteMessage.data["body"])
            var sp = getSharedPreferences("Messageing", MODE_PRIVATE)
            var editor = sp.edit()
            editor.putString("SendMsg", remoteMessage.data["body"]);
            editor.putBoolean("msgSet", true)
            editor.commit()
        }

        // Check if message contains a notification payload.
        if (remoteMessage.notification != null) {
            Log.e(
                TAG, "Message Notification Body: " + remoteMessage.notification!!.body
            )
            sendNotification(remoteMessage.notification!!.body)
        }
        onDeletedMessages()
    }

    // [END receive_message]
    override fun onNewToken(token: String) {
        Log.e(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // FCM registration token to your app server.
        sendRegistrationToServer(token)
    }
    // [END on_new_token]
    /**
     * Handle time allotted to BroadcastReceivers.
     */
    private fun handleNow() {
        Log.d(TAG, "Short lived task is done.")
        val intent = Intent(this@FcmReceiveService, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)
    }

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM registration token with any
     * server-side account maintained by your application.
     *
     * @param token The new token.
     */
    private fun sendRegistrationToServer(token: String) {
        // TODO: Implement this method to send token to your app server.
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     * @param messageBody FCM message body received.
     */
    private fun sendNotification(messageBody: String?) {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        Log.e(TAG, "mesg=${messageBody}")

        val pendingIntent = PendingIntent.getActivity(
            this, 0 /* Request code */,
            intent, PendingIntent.FLAG_IMMUTABLE
        )
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        val channelId = getString(R.string.default_notification_channel_id)
        val channelName: CharSequence = getString(R.string.default_notification_channel_name)
        val importance = NotificationManager.IMPORTANCE_LOW
        val notificationChannel = NotificationChannel(channelId, channelName, importance)
        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.BLUE
        notificationChannel.enableVibration(true)
        notificationChannel.vibrationPattern =
            longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
        notificationManager.createNotificationChannel(notificationChannel)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.ic_logo645_foreground)
            .setContentTitle(getString(R.string.fcm_message))
            .setContentText(messageBody)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .extend(
                NotificationCompat.WearableExtender()
                    .setBridgeTag("Foo")
                    .setContentIcon(R.mipmap.ic_logo645_foreground)
            )
            .setContentIntent(pendingIntent)

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Channel human readable title",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        // Dismiss notification once the user touches it.
        notificationBuilder.setAutoCancel(true)
        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }

}

필요한 부분만 일부 수정을 했다.

 

이번에 또 알게 된 부분이라면 전송할 때 코드 구현에 따라 수신되는 위치가 달라진다는 것이다. 

 

<data로 구현했을 때>

def lambda_handler(token, context):
    FCMToken = token

    Data = {'data': {'title': 'Lotto 보내 드립니다.', 'body': context}, 'to': FCMToken}
    Headers = {'Content-type': 'application/json',
               'Authorization': 'Key=AAAAR_..........................3uTasJ-DGfJKZkS-ccyNr0xhRHTepcuk4GaFoNMTADl4jvNFM1HYIRqzSLs219BxVA-T9frSd3VCSUIRXXn1PSxhOKgqroBVqTaxmWk'}
    http = urllib3.PoolManager().request('POST', 'https://fcm.googleapis.com/fcm/send', headers=Headers,
                                         body=json.dumps(Data))

    return {'statusCode': 200, 'body': json.dumps('Hello from Lambda!')}

 

<notification으로 구현했을 때>

def lambda_handler(token, context):
    FCMToken = token

    Data = {'notification': {'title': 'Lotto 보내 드립니다.', 'body': context}, 'to': FCMToken}
    Headers = {'Content-type': 'application/json',
               'Authorization': 'Key=AAAAR_0..........................uTasJ-DGfJKZkS-ccyNr0xhRHTepcuk4GaFoNMTADl4jvNFM1HYIRqzSLs219BxVA-T9frSd3VCSUIRXXn1PSxhOKgqroBVqTaxmWk'}
    http = urllib3.PoolManager().request('POST', 'https://fcm.googleapis.com/fcm/send', headers=Headers,
                                         body=json.dumps(Data))

    return {'statusCode': 200, 'body': json.dumps('Hello from Lambda!')}

같은 코드 이기는 하지만, 전송하는 parameter에 따라서 그걸 수신 앱에서는 다른 처리를 할 수 있다는 것이 된다. 

그래서 앞으로는 필요에 따라서 parameter을 다르게 해서 전송할 생각이다. 

 

 

notification 으로 호출할 때

 

body 로 호출 할 때

 

두 영상의 미묘한 차이를 찾을 수 있을까???   위에 기술한 fcmReceiceService 코드를 같이 보면서 이해를 해 보면 작은 차이을 알 수 있지 않을까 싶다.

 

오늘도 즐 코딩 ...

반응형