-
Notifications
You must be signed in to change notification settings - Fork 1
FCM 수신 및 딥링크 처리 구현 #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
59f8a47
✨ Feat: :core:navigation-contract 생성
dogmania 6e595dd
✨ Feat: :core:notification 생성
dogmania ddb2024
✨ Feat: :core:device-contract 생성
dogmania 4050ec1
✨ Feat: fcm, kermit, :core:notification 의존성 추가
dogmania 8cabcb6
✨ Feat: FirebaseMessagingService 서비스 등록
dogmania 461ce01
✨ Feat: Fcm 토큰 등록 로직 구현
dogmania 061b270
✨ Feat: notificationModule, coroutineModule 초기화
dogmania fd3b30b
✨ Feat: NotificationIntent 처리 메서드 호출
dogmania 1ef8d24
✨ Feat: :core:device-contract 의존성 추가
dogmania 14aa4c4
✨ Feat: :domain, :core:navigation-contract 의존성 추가
dogmania 1ae5440
✨ Feat: DeviceIdProvider 구현
dogmania 24c0a43
✨ Feat: DeviceIdProvider Koin 컨테이너 등록
dogmania b489385
♻️ Refactor: java.library -> android.library로 변경
dogmania 0c6761b
✨ Feat: 딥링크 처리 결과에 따른 네비게이션 로직 구현
dogmania 9e9cb0f
✨ Faet: 필요 의존성 추가
dogmania 1680969
✨ Feat: 전역 코루틴 모듈 생성
dogmania 768d463
✨ Feat: :core:navigation, :core:notification 의존 관계를 분리하기 위한 인터페이스 정의
dogmania ddb4c90
✨ Feat: :core:datastore, :core:notification 의존 관계를 분리하기 위한 인터페이스 정의
dogmania 76f2368
✨ Feat: FirebaseMessagingService 구현
dogmania 376a4f1
✨ Feat: 시스템 알림 채널 생성 클래스 구현
dogmania 9d4faea
✨ Feat: 딥링크 네비게이션 sealed interface 정의
dogmania 66c322d
✨ Feat: 딥링크 파싱 클래스 구현
dogmania d968130
✨ Feat: PushPayload Model 구현
dogmania 168c994
✨ Feat: Intent에 따른 deepLink 이벤트 관리 클래스 구현
dogmania 90ad2b4
✨ Feat: fcm 토큰 관리 클래스 구현
dogmania 0d4f27f
✨ Feat: 딥링크 파싱 후 네비게이션, 알림 읽음 처리 수행 클래스 구현
dogmania d6853e9
✨ Feat: NotificationModule 구현
dogmania 6dc6a3a
🐛 Fix: conflict 해결
dogmania 3c3ae24
♻️ Refactor: ktlint 적용
dogmania 3ec06a6
♻️ Refactor: 이름 변경
dogmania 7a615ba
✨ Feat: 알림 권한 추가
dogmania f87f0ff
✨ Feat: 알림 권한 체크 로직 추가
dogmania b459840
🔥 Remove: 불필요한 의존성 제거
dogmania 506bd4e
🔥 Remove: 불필요한 플러그인 제거
dogmania d2771c6
✨ Feat: 파싱 실패 시 로그 추가
dogmania 9486a71
♻️ Refactor: 에러 출력 방식 수정
dogmania 9c2ca7e
🐛 Fix: conflict 해결
dogmania 635683a
Merge branch 'develop' of github.com:YAPP-Github/Twix-Android into fe…
dogmania File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,27 @@ | ||
| package com.yapp.twix | ||
|
|
||
| import android.app.Application | ||
| import co.touchlab.kermit.Logger | ||
| import com.kakao.sdk.common.KakaoSdk | ||
| import com.twix.notification.token.NotificationTokenRegistrar | ||
| import com.yapp.twix.di.initKoin | ||
| import org.koin.java.KoinJavaComponent.getKoin | ||
|
|
||
| class TwixApplication : Application() { | ||
| private val logger = Logger.withTag("TwixApplication") | ||
|
|
||
| override fun onCreate() { | ||
| super.onCreate() | ||
|
|
||
| initKoin( | ||
| context = this, | ||
| ) | ||
| KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY) | ||
|
|
||
| try { | ||
| getKoin().get<NotificationTokenRegistrar>().registerCurrentToken() | ||
| } catch (e: Exception) { | ||
| logger.e(e) { "FCM token 등록 실패" } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.yapp.twix.di | ||
|
|
||
| import kotlinx.coroutines.CoroutineScope | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.SupervisorJob | ||
| import org.koin.core.qualifier.named | ||
| import org.koin.dsl.module | ||
|
|
||
| /** | ||
| * 전역 코루틴이 필요한 상황 | ||
| * · 전역/장수/인프라 객체(fcm 토큰 등록 객체, 알림 딥링크 네비게이션 처리 객체 등)가 독립적인 코루틴을 생성하면 수명 제어가 안됨 | ||
| * */ | ||
| val coroutineModule = | ||
| module { | ||
| single<CoroutineScope>(named("AppScope")) { | ||
| CoroutineScope(SupervisorJob() + Dispatchers.IO) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
app/src/main/java/com/yapp/twix/service/TwixFirebaseMessagingService.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| package com.yapp.twix.service | ||
|
|
||
| import android.Manifest | ||
| import android.app.PendingIntent | ||
| import android.content.Intent | ||
| import androidx.annotation.RequiresPermission | ||
| import androidx.core.app.NotificationCompat | ||
| import androidx.core.app.NotificationManagerCompat | ||
| import androidx.core.net.toUri | ||
| import co.touchlab.kermit.Logger | ||
| import com.google.firebase.messaging.FirebaseMessagingService | ||
| import com.google.firebase.messaging.RemoteMessage | ||
| import com.twix.designsystem.R | ||
| import com.twix.notification.channel.TwixNotificationChannelManager | ||
| import com.twix.notification.model.PushPayload | ||
| import com.twix.notification.model.toTwixPushPayload | ||
| import com.twix.notification.routing.NotificationLaunchDispatcher | ||
| import com.twix.notification.token.NotificationTokenRegistrar | ||
| import com.yapp.twix.main.MainActivity | ||
| import org.koin.core.component.KoinComponent | ||
| import org.koin.core.component.inject | ||
|
|
||
| class TwixFirebaseMessagingService : | ||
| FirebaseMessagingService(), | ||
| KoinComponent { | ||
| private val logger = Logger.withTag("TwixFirebaseMessagingService") | ||
| private val tokenRegistrar: NotificationTokenRegistrar by inject() | ||
| private val notificationChannelManager: TwixNotificationChannelManager by inject() | ||
|
|
||
| override fun onNewToken(token: String) { | ||
| super.onNewToken(token) | ||
| // 토큰 갱신 시 재등록 | ||
| tokenRegistrar.registerFcmToken(token) | ||
| } | ||
|
|
||
| @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) | ||
| override fun onMessageReceived(message: RemoteMessage) { | ||
| super.onMessageReceived(message) | ||
|
|
||
| val payload = message.data.toTwixPushPayload() | ||
|
|
||
| // 앱 실행 중에 토스트나 인앱 배너를 렌더링할 때 여기에서 분기처리하면 됨 | ||
| showSystemNotification(payload) | ||
| } | ||
|
|
||
| @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) | ||
| private fun showSystemNotification(payload: PushPayload) { | ||
| val manager = NotificationManagerCompat.from(this) | ||
| if (!manager.areNotificationsEnabled()) return | ||
|
|
||
| notificationChannelManager.ensureDefaultChannel() | ||
|
|
||
| val intent = | ||
| Intent(this, MainActivity::class.java).apply { | ||
| action = NotificationLaunchDispatcher.ACTION_NOTIFICATION_CLICK | ||
| flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP | ||
| putExtra(NotificationLaunchDispatcher.EXTRA_DEEP_LINK, payload.deepLink) | ||
| putExtra(NotificationLaunchDispatcher.EXTRA_FROM_PUSH_CLICK, true) | ||
| } | ||
|
|
||
| val stableId = payload.resolveStableNotificationId() | ||
| val pendingIntent = | ||
| PendingIntent.getActivity( | ||
| this, | ||
| stableId, | ||
| intent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, | ||
| ) | ||
|
|
||
| val notification = | ||
| NotificationCompat | ||
| .Builder(this, TwixNotificationChannelManager.CHANNEL_DEFAULT) | ||
| .setSmallIcon(R.drawable.ic_app_logo) | ||
| .setContentTitle(payload.title ?: getString(com.yapp.twix.R.string.app_name)) | ||
| .setContentText(payload.body.orEmpty()) | ||
| .setAutoCancel(true) | ||
| .setContentIntent(pendingIntent) | ||
| .setPriority(NotificationCompat.PRIORITY_HIGH) | ||
| .build() | ||
|
|
||
| NotificationManagerCompat | ||
| .from(this) | ||
| .notify(stableId, notification) | ||
| } | ||
|
|
||
| // PendingIntent의 requestCode에 활용될 안전한 notificationId 변환 메서드 | ||
| private fun PushPayload.resolveStableNotificationId(): Int { | ||
| val fromDeepLink = | ||
| deepLink | ||
| ?.let { | ||
| try { | ||
| it.toUri() | ||
| } catch (e: Exception) { | ||
| logger.e(e) { "deepLink 파싱 실패: $it" } | ||
| null | ||
| } | ||
| }?.getQueryParameter("notificationId") | ||
| ?.toLongOrNull() | ||
|
|
||
| if (fromDeepLink != null) { | ||
| val normalized = (fromDeepLink % Int.MAX_VALUE).toInt() | ||
| return if (normalized <= 0) 1 else normalized | ||
| } | ||
|
|
||
| val fallbackSeed = deepLink ?: title ?: body ?: System.currentTimeMillis().toString() | ||
| val hash = fallbackSeed.hashCode() | ||
| return if (hash == Int.MIN_VALUE) 0 else kotlin.math.abs(hash) | ||
| } | ||
dogmania marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
core/datastore/src/main/java/com/twix/datastore/deviceid/DeviceId.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.twix.datastore.deviceid | ||
|
|
||
| import androidx.datastore.core.Serializer | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.withContext | ||
| import kotlinx.serialization.Serializable | ||
| import kotlinx.serialization.SerializationException | ||
| import kotlinx.serialization.json.Json | ||
| import java.io.InputStream | ||
| import java.io.OutputStream | ||
|
|
||
| @Serializable | ||
| data class DeviceId( | ||
| val deviceId: String = "", | ||
| ) | ||
|
|
||
| internal object DeviceIdSerializer : Serializer<DeviceId> { | ||
| private val json = | ||
| Json { | ||
| ignoreUnknownKeys = true | ||
| isLenient = true | ||
| } | ||
|
|
||
| override val defaultValue: DeviceId | ||
| get() = DeviceId() | ||
|
|
||
| override suspend fun readFrom(input: InputStream): DeviceId = | ||
| try { | ||
| withContext(Dispatchers.IO) { | ||
| json.decodeFromString( | ||
| deserializer = DeviceId.serializer(), | ||
| string = input.readBytes().decodeToString(), | ||
| ) | ||
| } | ||
| } catch (e: SerializationException) { | ||
| defaultValue | ||
| } | ||
|
|
||
| override suspend fun writeTo( | ||
| t: DeviceId, | ||
| output: OutputStream, | ||
| ) { | ||
| withContext(Dispatchers.IO) { | ||
| output.write( | ||
| json | ||
| .encodeToString( | ||
| serializer = DeviceId.serializer(), | ||
| value = t, | ||
| ).encodeToByteArray(), | ||
| ) | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일전에 DataStore 리뷰할 때 현수가
AuthTokenProvider에 코루틴을 전역으로 주입해서 사용하면좋겠다고 의견을 줘서 app/di/AppModule 에 named만 지정하지 않은 코루틴 주입 객체가 있는데
혹시 이거랑 다른걸까 ?