Skip to content

알림 탭이 목적지로 갈 수 없는 구조 — MainActivity onNewIntent 부재, PendingIntent 빌더 2벌 중복 #1111

Open
1 of 1 issue completed

Description

@OnlyReadOneBook

📜 Overview (Required)

알림 2종(일일 알림 · FCM)이 각각 PendingIntent 를 만들지만 둘 다 «앱을 띄우는» 것 말고는 아무것도 못 한다. 목적지를 실어 보낼 수단도, 받을 수단도 없다.

받는 쪽인 MainActivity 에 onNewIntent 가 없고, 보내는 쪽 빌더는 두 모듈에 복붙 2벌로 갈려 있다. #924 (앱링크 전제 Nav3 전환) 가 «FCM 목적지 미연결» 을 근거로 들고 있는데, 그 목적지 배선이 Nav 방식과 무관하게 지금 구조에서는 원천적으로 불가능하다.


현황 1 — 받을 구조가 없다

MainActivity.kt (총 50줄)

항목 상태
onNewIntent 오버라이드 없음
intent 를 읽는 곳 L27 BuildConfig.DEBUG 전용 EXTRA_DEBUG_START_TIMELETTER 하나뿐
매니페스트 launchMode (L75-86) 미지정 = standard
intent-filter MAIN / LAUNCHER 만 (딥링크 없음 — 카카오 OAuth VIEW 는 SDK 액티비티 몫)

앱이 이미 떠 있는 상태에서 알림을 누르면 새 Intent 는 onNewIntent 로 도착하는데, 그걸 받는 코드가 없다. 즉 목적지 파라미터를 보내더라도 읽을 지점이 존재하지 않는다.

현황 2 — 보내는 쪽이 2벌, 둘 다 목적지 없음

동일 로직이 모듈을 건너 복붙돼 있다.

위치 함수
DailyNotificationWorker.kt#L59-L74 (core:common) buildContentPendingIntent()
AfternoteFirebaseMessagingService.kt#L78-L93 (app) buildContentPendingIntent(notificationId)

양쪽 모두 getLaunchIntentForPackage() + FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP + FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT 로 완전히 동일하다. 차이는 requestCode 뿐 (Worker 는 상수 0, FCM 은 messageId.hashCode()).

FCM 쪽은 RemoteMessage.data 를 이미 받고 있으면서도 FcmNotificationContentResolver 가 title · body 만 꺼내 쓰고 목적지 키는 보지 않는다.

현황 3 — standard + CLEAR_TOP 조합 (추론, 착수 시 실측)

MainActivity 는 launchMode 가 standard 인데 알림 인텐트에는 FLAG_ACTIVITY_CLEAR_TOP 이 붙는다. SINGLE_TOP 없이 이 조합이면 대상 액티비티가 finish 후 재생성되는 것이 문서상 동작이고, 이 앱은 단일 Activity + Compose Navigation 이라 재생성 = Compose 백스택 전체 소실이다.

다만 ACTION_MAIN / CATEGORY_LAUNCHER 인텐트는 시스템이 «태스크 재개» 로 달리 처리할 여지가 있어, 이 항목은 코드 정독 기반 추론이다. 착수 시 에뮬레이터 실측으로 확정한다.

현황 1·2 는 실측 불필요한 확정 사실이다.


작업

  1. PendingIntent 빌더를 1벌로 통합 — 목적지(route/파라미터)를 extra 로 실을 수 있는 형태로. core:common 은 app 에 의존할 수 없으므로 경계 설계 필요 (현재 getLaunchIntentForPackage() 우회의 이유)
  2. MainActivity 에 onNewIntent + 인텐트 → 목적지 해석 경로 추가
  3. launchMode 와 인텐트 플래그 조합을 실측 결과에 맞춰 확정 (singleTop 채택 여부 포함)
  4. FCM data 페이로드의 목적지 키 계약을 BE 와 확정하고 FcmNotificationContentResolver 확장
  5. requestCode 충돌 정리 — Worker 상수 0 과 FCM hashCode() 의 동일 값 충돌 가능성

완료 조건

  • 앱이 꺼진 상태 / 백그라운드 / 포그라운드 3개 상태에서 알림 탭 → 지정 목적지 도달을 각각 실측
  • 백그라운드에서 탭했을 때 기존 화면 스택이 유지되는지 확인 (현황 3 실측 결과 반영)
  • 빌더가 1벌만 남고 두 알림 경로가 그것을 공유
  • 목적지 키가 없거나 해석 실패한 알림은 홈으로 폴백 (크래시·빈 화면 금지)

📌 Child Issue (Optional)

No response

📍 Note (Optional)

Activity

  1. OnlyReadOneBook commented on Aug 27, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    선행 인프라 범위는 #1309로 분리했습니다.

    • 공용 notification PendingIntent 계약과 occurrence identity
    • cold/warm Intent 수신 및 인증 경계 이후 기존 NavHost 이동
    • 목적지 whitelist, malformed fallback, 중복 소비 방지
    • warm 로그인 전/후 계측 테스트

    BE FCM 목적지 key/value·route parameter schema와 data-only 등 background 전달 방식이 확정되지 않아 해당 값은 추측하지 않았습니다. 실제 시스템 알림 탭 기반 cold/background·Activity 재생성·기존 back stack 보존 실측도 이 이슈의 후속 범위로 남깁니다.

  2. OnlyReadOneBook commented on Aug 27, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    BE 목적지 계약과 무관한 공용 PendingIntent identity·MainActivity cold/warm 수신·Activity 재사용 검증 범위를 #1310으로 분리했습니다. #1111에는 FCM data 목적지 계약과 실제 feature 라우팅을 남깁니다.

  3. changed the title [-]fix(app): 알림 탭이 목적지로 갈 수 없는 구조 — MainActivity onNewIntent 부재, PendingIntent 빌더 2벌 중복[/-] [+]알림 탭이 목적지로 갈 수 없는 구조 — MainActivity onNewIntent 부재, PendingIntent 빌더 2벌 중복[/+] on Aug 28, 2026
  4. OnlyReadOneBook commented on Aug 29, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    백그라운드 FCM payload 형태(notification vs data-only)와 목적지 data 키 계약은 BE 쪽에 기록이 없어 Afternote/Afternote-BE#261 로 신설했다(assignee Hwanggyuun). 이 이슈의 잔여 범위 중 «BE 목적지 계약 → 내부 NotificationIntentContract 변환» 은 그 결정이 나온 뒤에야 착수 가능하다.

    근거(2026-08-29 BE main aba24df 실측) — FcmPushSender.sendOne 이 setNotification(...) 을 무조건 세팅하므로 앱이 백그라운드면 SDK 가 트레이에 직접 그리고 onMessageReceived() 를 건너뛴다. 즉 #1310/PR #1312 가 붙인 진입 인프라는 현재 계약에서 포그라운드 전용이다. 실서비스 발송 트리거는 BE 에 아직 없고 호출부는 AdminPushTestService(data={"type":"TEST"}) 하나뿐이다.

  5. OnlyReadOneBook commented on Aug 29, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    실측 갱신 (2026-08-30, origin/develop) — 본문의 「현황」 두 항목은 해소됐다

    이 이슈 본문은 c897b708 기준이라 지금 상태와 어긋난다. 다음 사람이 낡은 현황을 믿고 재조사하지 않도록 기록해 둔다.

    현황 1 「받을 구조가 없다」 — 해소

    MainActivity.kt:55 에 onNewIntent 오버라이드가 있다. 인텐트를 읽어 해석하는 내부 계약도 app/.../notification/NotificationIntentContract.kt 로 서 있다 — NotificationEntrySource(daily · fcm)와 NotificationEntryRequest(source + occurrence, 중복 소비를 막는 identityKey 포함).

    현황 2 「보내는 쪽이 2벌」 — 해소

    core/common/.../notification/NotificationPendingIntentFactory.kt 한 벌로 모였고, 일일 알림(DailyNotificationWorker)과 FCM(AfternoteFirebaseMessagingService)이 그것을 공유한다.

    남은 것 — 4번(목적지 키)뿐이고 BE 계약 대기다

    NotificationIntentContract 의 KDoc 이 경계를 명시하고 있다.

    실제 화면 목적지는 #1111의 BE payload 계약이 확정된 뒤 별도 adapter에서 추가한다. 이 단계에서는 출처와 occurrence만 보존해 Activity 수명주기와 PendingIntent identity를 목적지 계약과 분리한다.

    그 계약은 Afternote/Afternote-BE#261(payload 형태 + 목적지 data 키)이 추적한다. 원 본문의 「4번은 BE 계약이 필요하다. 계약 확정 전이면 1·2·3·5 만 먼저 착수하고 4는 분리한다」에서 앞부분이 끝나고 4만 남은 상태다.

    도달 자체가 아직 안 되는 문제는 BE#266 이 따로 본다.

    이 이슈는 그 잔여(목적지 배선 + 3개 앱 상태 실측)를 계속 추적한다.

  6. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    「작업」·「완료 조건」 전 항목 재판정 (2026-08-30, origin/develop fc8f32cd5)

    앞 코멘트는 본문 「현황 1·2」만 갱신했다. 이번엔 「작업」 1~5 와 「완료 조건」 4개를 같은 리비전에 대고 전수 대조했다. 결론 — 인프라 축은 전부 닫혔고, 남은 건 «목적지» 한 축이다. 그리고 그 한 축을 배선할 코드 자리가 지금 열린 PR 에 잡혀 있어 이번엔 코드를 넣지 않는다.

    「작업」 항목별

    # 항목 판정 근거
    1 앞 PendingIntent 빌더 1벌 통합 해소 전 모듈 main 소스셋에서 PendingIntent.getActivity 호출부는 core/common/.../notification/NotificationPendingIntentFactory.kt:50 1건. af909a635 (#1310)
    1 뒤 목적지(route/파라미터)를 extra 로 실을 수 있는 형태 남음 create(context, source, occurrenceToken) 에 목적지 파라미터가 없고, extras 는 ENTRY·SOURCE·OCCURRENCE_TOKEN 3개뿐이다 (NotificationPendingIntentFactory.kt:24-26)
    2 앞 MainActivity.onNewIntent 해소 MainActivity.kt:55. cold 진입은 onCreate 의 enqueueNotificationIntent(intent) (L29) 가 같은 경로로 흡수한다
    2 뒤 인텐트 → 목적지 해석 경로 남음 — 이 이슈의 본체 아래 별항
    3 launchMode · 인텐트 플래그 조합 확정 해소 manifest 는 손대지 않고(AndroidManifest.xml 의 .MainActivity 블록에 launchMode 여전히 미지정 = standard), 알림 인텐트에만 NEW_TASK | CLEAR_TOP | SINGLE_TOP 을 건다 (NotificationPendingIntentFactory.kt:42-46, 결정 근거는 같은 파일 KDoc L15-18). 본문 「현황 3」이 우려한 finish-후-재생성은 SINGLE_TOP 추가로 닫혔다
    4 FCM data 목적지 키 계약 + FcmNotificationContentResolver 확장 남음 — BE 차단 Resolver 는 아직 title·body 만 읽는다 (AfternoteFirebaseMessagingService.kt 의 FcmNotificationContentResolver). 계약은 Afternote/Afternote-BE#261 (OPEN, 코멘트 0). 이 이슈에 blocked_by 로 이미 걸려 있다
    5 requestCode 충돌 정리 해소 request code 는 상수 0 하나로 모으고, PendingIntent 동일성은 action 에 길이 prefix 로 넣은 source+occurrence 로 가른다 (NotificationPendingIntentFactory.kt:59-70). 회귀 가드는 NotificationNavigationAndroidTest#sameRequestCode_differentSourceAndOccurrenceKeepLatestExtras

    본체 — 「extras 를 읽기만 하고 아무 데도 안 보낸다」가 실측된다

    MainActivity 가 extras 를 파싱해 MainViewModel.enqueueNotificationEntry() 로 넣는 데까지는 간다. 그런데 그 큐를 꺼내 쓰는 코드가 프로덕션에 0건이다.

    $ git grep -n "pendingNotificationEntry\|consumeNotificationEntry" origin/develop -- '*.kt'
    app/src/main/java/com/afternote/afternote_fe/MainViewModel.kt      ← 선언·자기 자신뿐
    app/src/androidTest/java/com/afternote/afternote_fe/NotificationColdStartAndroidTest.kt:75
    app/src/test/java/com/afternote/afternote_fe/MainViewModelNotificationTest.kt
    

    main 소스셋 소비자 0건이고 consumeNotificationEntry() 도 테스트에서만 불린다. AppNavigation·AppState 어디에도 알림 기반 이동이 없다. 즉 알림을 탭하면 앱이 뜨고 이벤트가 큐에 들어간 뒤 그대로 멈춘다. 목적지 이동은 한 줄도 없다.

    이건 낡은 정보가 아니라 #1310 이 의도적으로 그은 경계다 — NotificationIntentContract KDoc 이 «실제 화면 목적지는 #1111의 BE payload 계약이 확정된 뒤 별도 adapter에서 추가한다» 고 명시한다. 아직 안 한 것이고, 그게 이 이슈에 남은 본체다.

    「완료 조건」 항목별

    조건 판정 근거
    빌더가 1벌만 남고 두 알림 경로가 공유 충족 DailyNotificationWorker·AfternoteFirebaseMessagingService 가 같은 팩토리를 부른다
    백그라운드 탭 시 기존 화면 스택 유지 가드는 섰으나 현재 red NotificationNavigationAndroidTest#backgroundNotification_resumesSameActivityAndKeepsCurrentScreen — PR #1576 의 Pixel 2 API 34 accessibility smoke 에서 실패 중이다(기대 실패 목록에 없는 androidTest 실패). 별도로 진단 중
    꺼진 상태 / 백그라운드 / 포그라운드 3개 상태에서 지정 목적지 도달 실측 미충족 도달할 목적지가 없다. 「진입」 자체는 NotificationColdStartAndroidTest(cold)·NotificationNavigationAndroidTest(warm·background)가 덮는다
    목적지 키 없음·해석 실패 → 홈으로 폴백 미충족 폴백 분기 자체가 없다. 아무것도 하지 않으므로 크래시·빈 화면은 없지만, 「홈으로 폴백」은 구현돼 있지 않다

    지금 코드를 넣지 않는 이유 — 착수 순서

    남은 몫을 배선하려면 손댈 자리가 둘이고, 각각 파일이 정해져 있다.

    1. 생산 측 — 팩토리에 목적지 파라미터 추가(core/common/.../NotificationPendingIntentFactory.kt) + 두 생산자(core/common/.../DailyNotificationWorker.kt, app/.../messaging/AfternoteFirebaseMessagingService.kt)
    2. 소비 측 — app/.../notification/NotificationIntentContract.kt 에 목적지 파싱·whitelist·Home 폴백, 그리고 그것을 AppNavigation 으로 잇는 adapter

    1·2 의 4개 파일이 전부 열린 PR #1576(refactor/1575 — 알림 발생 식별자 occurrenceToken → occurrenceId)의 변경 파일이다. 회귀 가드인 NotificationNavigationAndroidTest.kt·NotificationIntentContractTest.kt·MainViewModelNotificationTest.kt 도 마찬가지다. 지금 같은 파일을 고치면 순수 리네임 PR 과 정면 충돌하고, 위 red 테스트까지 겹쳐 진단이 꼬인다.

    그래서 착수 순서를 이렇게 둔다.

    1. PR refactor(app,core): 알림 발생 식별자를 occurrenceId 로 부른다 (#1575) #1576 머지 (식별자 리네임이 먼저 안착해야 한다)
    2. BE#261 계약 확정 — whitelist 에 넣을 목적지 값 집합과 파라미터 타입 규약이 여기서 나온다
    3. 이 이슈 구현 — 생산·소비 양측 배선 + 3개 상태 실측

    내부 배선(1·2)만 떼어 BE#261 전에 세울 수도 있지만, whitelist 에 담을 값이 BE#261 이 정하는 것이라 껍데기만 남는다. 게다가 목적지 이동을 넣는 순간 위 backgroundNotification_...KeepsCurrentScreen 가드(«알림을 받아도 현재 화면을 유지한다» = #1310 단계의 계약)를 이 이슈 계약(«지정 목적지로 이동한다»)으로 갈아끼워야 한다. 그 파일이 지금 #1576 과 red 진단이 동시에 잡고 있는 파일이라, 순서를 지키는 편이 싸다.

    곁가지

    도달 자체(발송 200 인데 기기 NotificationRecord 0건)는 Afternote/Afternote-BE#266 이 따로 본다. 그게 풀리기 전에는 FCM 경로의 3개 상태 실측이 물리적으로 불가능하다 — 실측 항목은 BE#266 뒤에 놓인다.

  7. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  8. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    목적지 배선을 PR #1641 로 올렸다 — 이 이슈는 계속 열어 둔다

    앞 코멘트(「「작업」·「완료 조건」 전 항목 재판정」)가 「본체」로 지목한 「extras 를 읽기만 하고 아무 데도 안 보낸다」 를 닫는다. base 는 develop 124547cb.

    넣은 것

    층 자리 내용
    계약 core/common/.../notification/NotificationDestination.kt (신규) 목적지 vocabulary — home · mind_record · time_letter · afternote
    생산 NotificationPendingIntentFactory destination 필수 파라미터 + EXTRA_NOTIFICATION_DESTINATION (= 「작업」 1번 뒤 절반)
    생산 DailyNotificationWorker 일일 알림 → MIND_RECORD
    수신 NotificationIntentContract 목적지 파싱 + 계약 밖 값 Home 폴백
    수신 app/.../navigation/NotificationEntryEffect.kt (신규) 큐 소비 → AppState.navigateToBottomBarRoute (= 「작업」 2번 뒤 절반)

    목적지 집합은 바텀바 최상위 4개로 제한했다. 알림은 앱 밖에서 오는 입력이라 어떤 화면이든 열 수 있게 두면 인자·권한 관문을 우회하는 통로가 된다. 그 경계를 NotificationDestinationRouteTest 가 고정한다.

    launchMode 는 그대로 뒀다(「작업」 3번은 #1310 이 이미 닫은 판정 유지). 「완료 조건」의 홈 폴백도 이 PR 이 닫는다.

    남는 것 — 이 이슈를 여는 이유

    1. 「작업」 4번 — FCM data 목적지 키. [feat] FCM 푸시 발송 계약 확정 — payload 형태(notification vs data-only)와 목적지 data 키 Afternote-BE#261 대기 그대로다. 키 이름을 추측해 두면 BE 가 다른 이름을 고르는 순간 조용히 죽는 분기가 남는다. FCM 은 HOME 을 명시하고, 계약이 서면 FcmNotificationContentResolver 에서 data 를 읽어 NotificationDestination 으로 옮기면 된다 — 그 자리는 이 PR 이 만들어 뒀다.
    2. 「완료 조건」의 실기기 3개 상태 실측. FCM 도달 자체가 아직 안 되므로([fix] FCM 발송이 200 인데 기기에 도달하지 않는다 — 서버 Firebase 프로젝트가 앱과 어긋난 것으로 보임 Afternote-BE#266) 물리적으로 불가능하다. 계측으로는 cold·background·foreground 세 경로를 모두 덮었고 api30 GMD 에서 5건 전부 통과를 실측했다.

    그래서 PR 본문은 Closes 가 아니라 Refs 다. 열린 blocked_by 두 건(BE#261 · #1575)이 있어 merge-order-guard 가 closing 링크를 막기도 한다.

    앞 코멘트가 적어 둔 착수 순서와 어긋난 점

    앞 코멘트는 「PR #1576 머지 → BE#261 확정 → 구현」 순서를 제안했다. 이번엔 그 중 BE 계약과 무관한 절반만 먼저 넣었다 — whitelist 에 담을 값이 FE 자체 화면이라 BE#261 없이도 껍데기가 아니고(일일 알림은 실제로 마음의 기록으로 간다), FCM 쪽만 키 계약을 기다리면 되기 때문이다.

    #1576 과는 6개 파일이 겹친다. 의존이 아니라 텍스트 충돌이라 스택으로 올리지 않았고, 먼저 머지되는 쪽 뒤에 나머지가 rebase 한다. 충돌 지점은 전부 같은 시그니처 줄이라 기계적이다.

    #1310 단계가 세운 계측 가드 2건(backgroundNotification_...KeepsCurrentScreen · warmNotificationWhileLoggedIn_preservesExistingNavHost)은 계약이 「현재 화면 유지」에서 「목적지 이동」으로 바뀌므로 이름과 단언을 함께 갈아끼웠다. Activity 재사용(assertSame) 단언은 그대로 남긴다.

  9. OnlyReadOneBook commented on Sep 2, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    「내부 목적지 축」(vocabulary·팩토리 destination·수신 파싱·홈 폴백·NavHost 연결·계측 5건)을 #1727 로 분리했고 PR #1641 이 그 이슈를 Closes 합니다.

    이 이슈에는 작업 4(FCM data 목적지 키 + Resolver 확장, Afternote-BE#261 대기)와 실기기 3개 상태 도달 실측(FCM 은 BE#266 닫힘 이후 도달 재확인)·cold 도달 단언이 남습니다. blocked_by BE#261 은 유지합니다.

  10. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:platformCI·빌드·릴리스·저장소 운영 영역bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions