fix(deeplink): 푸시 딥링크 우선 분기로 혼합 라우팅을 방지한다
This commit is contained in:
@@ -75,7 +75,12 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
|
||||
}
|
||||
}
|
||||
|
||||
val deepLinkExtras = android.os.Bundle().apply {
|
||||
val deepLinkExtras = if (!deepLinkUrl.isNullOrBlank()) {
|
||||
android.os.Bundle().apply {
|
||||
putString("deep_link", deepLinkUrl)
|
||||
}
|
||||
} else {
|
||||
android.os.Bundle().apply {
|
||||
messageData["room_id"]?.let { putString("room_id", it) }
|
||||
messageData["message_id"]?.let { putString("message_id", it) }
|
||||
messageData["content_id"]?.let { putString("content_id", it) }
|
||||
@@ -84,6 +89,7 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
|
||||
messageData["deep_link_value"]?.let { putString("deep_link_value", it) }
|
||||
messageData["deep_link_sub5"]?.let { putString("deep_link_sub5", it) }
|
||||
}
|
||||
}
|
||||
|
||||
if (!deepLinkExtras.isEmpty) {
|
||||
intent.putExtra(Constants.EXTRA_DATA, deepLinkExtras)
|
||||
|
||||
@@ -5,9 +5,15 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||
import kr.co.vividnext.sodalive.app.SodaLiveApp
|
||||
import kr.co.vividnext.sodalive.audition.AuditionActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
|
||||
import kr.co.vividnext.sodalive.message.MessageActivity
|
||||
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentActivity
|
||||
import kr.co.vividnext.sodalive.splash.SplashActivity
|
||||
import java.util.Locale
|
||||
@@ -18,6 +24,7 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
|
||||
val data: Uri? = intent?.data
|
||||
val deepLinkExtras = buildDeepLinkExtras(intent)
|
||||
val deepLinkUrl = deepLinkExtras?.getString("deep_link")
|
||||
|
||||
if (data != null && data.scheme != null) {
|
||||
val host = data.host
|
||||
@@ -46,6 +53,15 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
if (SodaLiveApp.isAppInForeground) {
|
||||
if (!deepLinkUrl.isNullOrBlank()) {
|
||||
deepLinkExtras?.let {
|
||||
if (routeForegroundDeepLink(it)) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, MainActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
@@ -70,6 +86,10 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
|
||||
val data = intent.data
|
||||
|
||||
data?.toString()?.takeIf { it.isNotBlank() }?.let {
|
||||
extras.putString("deep_link", it)
|
||||
}
|
||||
|
||||
fun putIfAbsent(key: String, value: String?) {
|
||||
if (!value.isNullOrBlank() && !extras.containsKey(key)) {
|
||||
extras.putString(key, value)
|
||||
@@ -106,6 +126,7 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
copyString("message_id")
|
||||
copyString("audition_id")
|
||||
copyString("content_id")
|
||||
copyString("deep_link")
|
||||
copyString("deep_link_value")
|
||||
copyString("deep_link_sub5")
|
||||
|
||||
@@ -142,6 +163,14 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
extras.putString("content_id", it.toString())
|
||||
}
|
||||
|
||||
intent.getStringExtra("deep_link")?.takeIf { it.isNotBlank() }?.let {
|
||||
extras.putString("deep_link", it)
|
||||
}
|
||||
|
||||
intent.getStringExtra("deepLink")?.takeIf { it.isNotBlank() }?.let {
|
||||
extras.putString("deep_link", it)
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
applyPathDeepLink(data = data, putIfAbsent = ::putIfAbsent)
|
||||
}
|
||||
@@ -186,6 +215,158 @@ class DeepLinkActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun routeForegroundDeepLink(bundle: Bundle): Boolean {
|
||||
val roomId = bundle.getString("room_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_ROOM_ID).takeIf { it > 0 }
|
||||
val channelId = bundle.getString("channel_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_USER_ID).takeIf { it > 0 }
|
||||
val messageId = bundle.getString("message_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_MESSAGE_ID).takeIf { it > 0 }
|
||||
val contentId = bundle.getString("content_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_AUDIO_CONTENT_ID).takeIf { it > 0 }
|
||||
val auditionId = bundle.getString("audition_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_AUDITION_ID).takeIf { it > 0 }
|
||||
val communityCreatorId = bundle.getString(Constants.EXTRA_COMMUNITY_CREATOR_ID)?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_COMMUNITY_CREATOR_ID).takeIf { it > 0 }
|
||||
|
||||
when {
|
||||
roomId != null && roomId > 0 -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, LiveRoomActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_ROOM_ID, roomId)
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
channelId != null && channelId > 0 -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, channelId)
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
contentId != null && contentId > 0 -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
messageId != null && messageId > 0 -> {
|
||||
startActivity(Intent(applicationContext, MessageActivity::class.java))
|
||||
return true
|
||||
}
|
||||
|
||||
communityCreatorId != null && communityCreatorId > 0 -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, communityCreatorId)
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
auditionId != null && auditionId > 0 -> {
|
||||
startActivity(Intent(applicationContext, AuditionActivity::class.java))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val deepLinkValue = bundle.getString("deep_link_value")
|
||||
val deepLinkValueId = bundle.getString("deep_link_sub5")?.toLongOrNull()
|
||||
return routeByDeepLinkValue(deepLinkValue = deepLinkValue, deepLinkValueId = deepLinkValueId)
|
||||
}
|
||||
|
||||
private fun routeByDeepLinkValue(deepLinkValue: String?, deepLinkValueId: Long?): Boolean {
|
||||
if (deepLinkValue.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return when (deepLinkValue.lowercase(Locale.ROOT)) {
|
||||
"series" -> {
|
||||
if (deepLinkValueId == null || deepLinkValueId <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_SERIES_ID, deepLinkValueId)
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"content" -> {
|
||||
if (deepLinkValueId == null || deepLinkValueId <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, deepLinkValueId)
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"channel" -> {
|
||||
if (deepLinkValueId == null || deepLinkValueId <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, deepLinkValueId)
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"live" -> {
|
||||
if (deepLinkValueId == null || deepLinkValueId <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, LiveRoomActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_ROOM_ID, deepLinkValueId)
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"community" -> {
|
||||
if (deepLinkValueId == null || deepLinkValueId <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, deepLinkValueId)
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"message" -> {
|
||||
startActivity(Intent(applicationContext, MessageActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
"audition" -> {
|
||||
startActivity(Intent(applicationContext, AuditionActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyPathDeepLink(
|
||||
data: Uri,
|
||||
putIfAbsent: (key: String, value: String?) -> Unit
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.ColorStateList
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
@@ -60,6 +61,7 @@ import java.util.Locale
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@UnstableApi
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) {
|
||||
@@ -312,6 +314,20 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
|
||||
}
|
||||
|
||||
private fun executeBundleDeeplink(bundle: Bundle): Boolean {
|
||||
val deepLinkUrl = bundle.getString("deep_link")
|
||||
if (!deepLinkUrl.isNullOrBlank()) {
|
||||
val deepLinkBundle = buildBundleFromDeepLinkUrl(deepLinkUrl)
|
||||
if (deepLinkBundle != null) {
|
||||
return executeBundleRoute(deepLinkBundle)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return executeBundleRoute(bundle)
|
||||
}
|
||||
|
||||
private fun executeBundleRoute(bundle: Bundle): Boolean {
|
||||
val roomId = bundle.getString("room_id")?.toLongOrNull()
|
||||
?: bundle.getLong(Constants.EXTRA_ROOM_ID).takeIf { it > 0 }
|
||||
val channelId = bundle.getString("channel_id")?.toLongOrNull()
|
||||
@@ -385,6 +401,95 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
|
||||
return false
|
||||
}
|
||||
|
||||
private fun buildBundleFromDeepLinkUrl(deepLinkUrl: String): Bundle? {
|
||||
val data = runCatching { deepLinkUrl.toUri() }.getOrNull() ?: return null
|
||||
val extras = Bundle().apply {
|
||||
putString("deep_link", deepLinkUrl)
|
||||
}
|
||||
|
||||
fun putQuery(key: String) {
|
||||
val value = data.getQueryParameter(key)
|
||||
if (!value.isNullOrBlank()) {
|
||||
extras.putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
putQuery("room_id")
|
||||
putQuery("channel_id")
|
||||
putQuery("message_id")
|
||||
putQuery("audition_id")
|
||||
putQuery("content_id")
|
||||
putQuery("deep_link_value")
|
||||
putQuery("deep_link_sub5")
|
||||
putQuery(Constants.EXTRA_COMMUNITY_CREATOR_ID)
|
||||
|
||||
applyPathDeepLink(data = data) { key, value ->
|
||||
if (!value.isNullOrBlank() && !extras.containsKey(key)) {
|
||||
extras.putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return extras
|
||||
}
|
||||
|
||||
private fun applyPathDeepLink(
|
||||
data: Uri,
|
||||
putIfAbsent: (key: String, value: String?) -> Unit
|
||||
) {
|
||||
val host = data.host?.lowercase(Locale.ROOT).orEmpty()
|
||||
val pathSegments = data.pathSegments.filter { it.isNotBlank() }
|
||||
|
||||
val pathType: String
|
||||
val pathId: String?
|
||||
|
||||
if (host.isNotBlank() && host != "payverse") {
|
||||
pathType = host
|
||||
pathId = pathSegments.firstOrNull()
|
||||
} else if (pathSegments.isNotEmpty()) {
|
||||
pathType = pathSegments[0].lowercase(Locale.ROOT)
|
||||
pathId = pathSegments.getOrNull(1)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
when (pathType) {
|
||||
"live" -> {
|
||||
putIfAbsent("room_id", pathId)
|
||||
putIfAbsent("deep_link_value", "live")
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
|
||||
"content" -> {
|
||||
putIfAbsent("content_id", pathId)
|
||||
putIfAbsent("deep_link_value", "content")
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
|
||||
"series" -> {
|
||||
putIfAbsent("deep_link_value", "series")
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
|
||||
"community" -> {
|
||||
putIfAbsent("deep_link_value", "community")
|
||||
putIfAbsent(Constants.EXTRA_COMMUNITY_CREATOR_ID, pathId)
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
|
||||
"message" -> {
|
||||
putIfAbsent("deep_link_value", "message")
|
||||
putIfAbsent("message_id", pathId)
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
|
||||
"audition" -> {
|
||||
putIfAbsent("deep_link_value", "audition")
|
||||
putIfAbsent("audition_id", pathId)
|
||||
putIfAbsent("deep_link_sub5", pathId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeOneLink() {
|
||||
val deepLinkValue = SharedPreferenceManager.marketingLinkValue
|
||||
val deepLinkValueId = SharedPreferenceManager.marketingLinkValueId
|
||||
|
||||
44
docs/20260313_푸시메시지터치딥링크우선처리.md
Normal file
44
docs/20260313_푸시메시지터치딥링크우선처리.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 2026-03-13 푸시 메시지 터치 딥링크 우선 처리
|
||||
|
||||
## 체크리스트
|
||||
- [x] 기존 푸시 터치/딥링크 라우팅 경로 분석
|
||||
- [x] 푸시 터치 시 `deep_link` 비어있지 않으면 딥링크 우선 실행, 비어 있으면 기존 로직 유지
|
||||
- [x] 앱 실행 중 딥링크 실행 시 메인 페이지 호출 없이 현재 페이지에서 목적지로 이동하도록 수정
|
||||
- [x] 앱 미실행 후 `MainActivity` 진입 로직에도 동일한 `deep_link` 우선 분기 반영
|
||||
- [x] `deep_link` 존재 시 번들에 `deep_link`만 넣고, 미존재 시 fallback(`room_id` 등)만 넣도록 상호배타 분기 적용
|
||||
- [x] `MainActivity` 딥링크/푸시 처리에서도 `deep_link` 존재 시 fallback과 병합하지 않도록 상호배타 분기 적용
|
||||
- [x] 정적 진단/빌드/테스트 및 결과 기록
|
||||
|
||||
## 검증 기록
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: 작업 착수 전 요구사항에 맞는 푸시 터치/딥링크 처리 지점을 찾기 위해 코드베이스 탐색을 시작했다.
|
||||
- 실행 명령: `grep(pattern="deep_link|deeplink|deepLink", path=".", include="*.{kt,kts,xml,java}")`, `grep(pattern="FirebaseMessagingService|notification|push|PendingIntent|MainActivity", path=".", include="*.{kt,java,xml}")`
|
||||
- 결과: `SodaFirebaseMessagingService`, `MainActivity`, `DeepLinkActivity`를 핵심 수정 후보로 확인했다.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: 푸시 탭 시 raw `deep_link` 유무로 분기하기 위해 FCM payload 전달/포그라운드 라우팅/MainActivity 파싱 로직을 함께 수정했다.
|
||||
- 실행 명령: `git diff -- app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`
|
||||
- 결과: `deep_link` 전달(`SodaFirebaseMessagingService`) + 포그라운드 직접 이동 분기(`DeepLinkActivity`) + cold start 시 `MainActivity`의 `deep_link` 우선 파싱 분기가 반영됨을 확인했다.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: 수정 파일 정적 진단 가능 여부를 확인했다.
|
||||
- 실행 명령: `lsp_diagnostics(filePath="app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt")`, `lsp_diagnostics(filePath="app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt")`, `lsp_diagnostics(filePath="app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt")`
|
||||
- 결과: 현재 실행 환경에서 Kotlin(`.kt`) LSP 서버가 미구성되어 진단을 수행할 수 없음을 확인했다.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: 변경으로 인한 컴파일/단위테스트 회귀 여부를 확인하기 위해 Debug 빌드와 단위 테스트를 실행했다.
|
||||
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: `BUILD SUCCESSFUL`.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: `deep_link`와 fallback 파라미터가 섞이지 않도록 FCM 번들 생성을 상호배타 분기로 변경했다.
|
||||
- 실행 명령: `grep(pattern="val deepLinkExtras = if \(!deepLinkUrl.isNullOrBlank\(\)\)|putString\(\"deep_link\"|messageData\[\"room_id\"\]", path="app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt", output_mode="content")`
|
||||
- 결과: `deep_link` 존재 시 `putString("deep_link", deepLinkUrl)`만 수행하고, fallback 필드(`room_id` 등)는 else 블록에서만 채워지도록 분리가 확인되었다.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: 상호배타 분기 변경 이후 컴파일/테스트 회귀 여부를 재검증했다.
|
||||
- 실행 명령: `lsp_diagnostics(filePath="app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt")`, `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: Kotlin LSP 서버 미구성으로 `.kt` 진단은 불가, Gradle 검증은 `BUILD SUCCESSFUL`.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: cold start 구간에서도 섞임이 없도록 `MainActivity.executeBundleDeeplink`의 `deep_link` 처리에서 기존 fallback 번들과 병합 로직을 제거했다.
|
||||
- 실행 명령: `grep(pattern="mergedBundle|putAll\\(deepLinkBundle\\)|return executeBundleRoute\\(deepLinkBundle\\)|return executeBundleRoute\\(bundle\\)", path="app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt", output_mode="content")`
|
||||
- 결과: `putAll(deepLinkBundle)`/병합 흔적 없이 `deep_link` 경로(`return executeBundleRoute(deepLinkBundle)`)와 fallback 경로(`return executeBundleRoute(bundle)`)가 상호배타로 분리됨을 확인했다.
|
||||
- 2026-03-13
|
||||
- 무엇/왜/어떻게: `MainActivity` 분기 수정 후 컴파일/단위 테스트 회귀를 재확인했다.
|
||||
- 실행 명령: `lsp_diagnostics(filePath="app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt")`, `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: Kotlin LSP 서버 미구성으로 `.kt` 진단은 불가, Gradle 검증은 `BUILD SUCCESSFUL`.
|
||||
Reference in New Issue
Block a user