Suhwanc

 

드디어 모바일 과목이 종강했다!

 

앞으로 안드로이드 스튜디오를 다시 켤 일이 없을 것 같긴 한데 (아니 삭제하기 전에)
마지막으로 프로젝트하면서 한글로 된 정보가 많이 없었던 Firebase fcm 서비스에 관련해 포스팅을 하고 마치도록 하겠다!

 

 

[시작 전 알고 있어야 할 내용들]

 

  • Firebase와 프로젝트 연결 방법
  • 기본적인 textview, button 넣는 방법 (xml 파일)
  • Firebase database 사용 방법 (push, set, addValueEventListener, datachange snapshot,...)

 

뭐 많이 요구하는 것 같으나 fcm까지 왔으면 위 내용을 모를 수가 없다고 생각한다.

 

시작 전에, 이 포스팅 내용에 대해 영상을 보면서 따라 하고자 하면 아래 링크를 참고하면 좋을 것 같다.

 

https://youtu.be/HoFWPPv1ih8

 

 

 

1. fcm set up

 

[Tools] - [Firebase] 클릭 후 "Cloud Messaging"을 눌러 프로젝트에 fcm 기본 설정을 추가해준다.

 

 

 

2. Gradle : 모듈 파일에 fcm, coroutines, retrofit implement 후 sync 해주기!

    //fcm
    implementation 'com.google.firebase:firebase-messaging:20.0.0'

    //Coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'

    //Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.6.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

 

Coroutines, Retrofit 은 차후 firebase api를 이용하기 위해 사용된다!

 

 

3. 사용할 레이아웃 만들어두기

 

이렇게 Plain Text와 버튼이 있는 구조면 된다.

이거는 프로젝트라 빡빡하게 한 거고.. 굳이 커스터마이징까지 할 필요는 없다! 

 

 

4. api 관련 키 값 등을 저장할 상수 데이터 클래스

 

class Constants {

    companion object {
        const val BASE_URL = "https://fcm.googleapis.com"
        const val SERVER_KEY = "???"
        const val CONTENT_TYPE = "application/json"
    }
}

 

요청을 보낼 BASE_URL과 fcm server key, 컨텐츠 타입을 넣는데 서버 키를 제외하고는 따라 쳐주시면 된다.

 

여러분의 서버 키는 다음과 같이 구할 수 있다.

 

우측 상단 톱니바퀴 클릭 -> Project settings

 

 

상단에 Cloud Messaging을 누르면 서버 키가 나온다. 그걸 위 코드에 붙여 넣어주면 된다.

 

 

5. api에 body로 전달할 데이터 클래스 만들기

data class NotificationData(
    val title: String,
    val message: String
)
data class PushNotification(
    val data: NotificationData,
    val to: String
)

 

각각 이렇게 데이터 클래스를 만들어준다.

 

NotificationData의 title과 message는 우리가 통상적으로 알림이 오면, 어플 이름 - 내용이 뜨는 것처럼

어플 이름 부분이 제목, 내용이 메시지가 되겠다.

 

PushNotification은 데이터로 위 두 가지를 받고, to 변수는 알림을 보낼 디바이스 토큰, 또는 채널이 된다.

채널은 "구독" 형식인데, 해당 채널을 구독하고 있는 디바이스들에게 모두 알림이 가는 형태이다.

 

 

6. NotificationAPI 호출

import com.example.streetcat.data.Constants.Companion.CONTENT_TYPE
import com.example.streetcat.data.Constants.Companion.SERVER_KEY
import com.example.streetcat.data.PushNotification
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface NotificationAPI {
    //서버 키와 보낼 형식을 헤더에 넣는다. (json)
    @Headers("Authorization: key=$SERVER_KEY", "Content-Type:$CONTENT_TYPE")
    @POST("fcm/send")
    suspend fun postNotification(
        @Body notification: PushNotification
    ): Response<ResponseBody>
}

 

이런 식으로 그대로 추가하면 된다. 따로 무언가 바꾸지 않아도 될 것이다.

 

 

7. Retrofit Instance 만들기

import com.example.streetcat.data.Constants.Companion.BASE_URL
import com.example.streetcat.activity.NotificationAPI
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RetrofitInstance {

    companion object {
        private val retrofit by lazy {
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }

        val api by lazy {
            retrofit.create(NotificationAPI::class.java)
        }
    }
}

이건.. 일단 이렇구나! 하고 넘어가자

 

 

8. 토큰 가져오기

 

아까 만든 레이아웃에 버튼을 누르면 아래 코드를 실행시키도록 리스너를 만들어주자

val key = mAuth!!.currentUser.uid
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener {
    val database = FirebaseDatabase.getInstance()
    database.getReference("users").child(key).child("token").setValue(it.token)
}

여긴 완전히 따라 하면 안 되고.. 코드 설명을 하자면

key는 현재 해당 유저의 uid

 

FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener <-- 이 부분은 현재 토큰을 가져오는 가장 기본적이고 쉬운 코드이다. 내부에서 it.token을 콘솔에 띄우면 해당 디바이스의 토큰 값이 나온다.

(참고로 FirebaseInstanceId에서 deprecated 되었다고 작대기가 그어지던데.. 일단 지금은 작동하긴 한다!)

 

마지막은 그 토큰을 db에 저장하는 코드이다.

 

 

9. 알람 띄우는 함수

private fun sendNotification(notification: PushNotification) = CoroutineScope(Dispatchers.IO).launch {
    try {
        val response = RetrofitInstance.api.postNotification(notification)
        if(response.isSuccessful) {
            Log.d(TAG, "Response: ${Gson().toJson(response)}")
        } else {
            Log.e(TAG, response.errorBody().toString())
        }
    } catch(e: Exception) {
        Log.e(TAG, e.toString())
    }
}

 

이제 아까 다 만들었던 것들을 쓸 수 있다.

 

 

10. 가지고 있는 토큰으로 9번 함수 호출

val PushNotification = PushNotification(
    NotificationData("StreetCat", "내가 쓴 게시글에 댓글이 달렸어요!"),
    token
)
sendNotification(PushNotification)

token은 db에서 가져온 string값 그대로이다.

sendNotification과 같은 파일에 있으면 그대로 작동할 것이다.

 

 

11. Fcm 관련 함수들

package com.example.streetcat.activity

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_ONE_SHOT
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.example.streetcat.R
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlin.random.Random

private const val CHANNEL_ID = "my_channel"

class FirebaseService : FirebaseMessagingService() {

    companion object {
        var sharedPref: SharedPreferences? = null

        var token: String?
        get() {
            return sharedPref?.getString("token", "")
        }
        set(value) {
            sharedPref?.edit()?.putString("token", value)?.apply()
        }
    }

    // 사용자 디바이스 토큰 새로 생성
    override fun onNewToken(newToken: String) {
        super.onNewToken(newToken)
        token = newToken
    }

    //알람이 온 경우 --> 어떻게 보이게 할 것인지, 누르면 어디로 이동하게 할 것인지 정하는 메소드
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        //알림 팝업 누를 시 MainActivity로 이동
        val intent = Intent(this, MainActivity::class.java)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val notificationID = Random.nextInt()

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(notificationManager)
        }

        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_ONE_SHOT)
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(message.data["title"])
            .setContentText(message.data["message"])
            .setSmallIcon(R.drawable.ic_cat_smile)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)
            .build()

        notificationManager.notify(notificationID, notification)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(notificationManager: NotificationManager) {
        val channelName = "channelName"
        val channel = NotificationChannel(CHANNEL_ID, channelName, IMPORTANCE_HIGH).apply {
            description = "My channel description"
            enableLights(true)
            lightColor = Color.GREEN
        }
        notificationManager.createNotificationChannel(channel)
    }
}

 

이 부분은 알람을 어떻게 띄울 것이고, 알람 팝업을 누르면 어떻게 이동할 것인지

그리고 토큰이 없는 경우 생성하는 법과 채널 만드는 법이 있다.

 

확실히 과정을 이해하지 못해서 그대로 두었는데, 이 부분도 꼭 필요하다.

나처럼 이해못하면 그냥 FcmService.kt 파일 하나 이렇게 만들면 작동할 것이다.

 

intent 부분과 icon 부분만 수정하면 그대로 써먹을 수 있을 듯하다..!

 

 

12. 결과물

 

 

 

댓글은 결국 내가 썼지만.. 이렇게 댓글을 달면, 에타처럼 게시물 작성자에게 알림이 오는 서비스를 구현할 수 있다.

 

 

팝업은 이런 식으로 뜬다 하핫

 

안드로이드 안녕~

'Android' 카테고리의 다른 글

MainActivity.kt 기본 설명  (0) 2021.01.29
문자열 리소스화 & 다국어 지원 기능  (4) 2021.01.27
Android Studio 프로젝트 구성  (0) 2021.01.27
코틀린 기본 문법  (0) 2021.01.25