Skip to content

refactor: FCM 토큰 및 알림 설정 동기화 로직 개선#226

Merged
seoyoon513 merged 3 commits intodevelopfrom
BOOK-437-refactor/#225
Nov 15, 2025
Merged

refactor: FCM 토큰 및 알림 설정 동기화 로직 개선#226
seoyoon513 merged 3 commits intodevelopfrom
BOOK-437-refactor/#225

Conversation

@seoyoon513
Copy link
Copy Markdown
Contributor

@seoyoon513 seoyoon513 commented Nov 14, 2025

🔗 관련 이슈

📙 작업 설명

  • 로컬에 저장된 FCM 토큰 비교 로직을 제거하고, 앱 진입 때마다 서버에 최신 토큰을 동기화하도록 변경
  • fcm 관련 dataStore 코드 제거
  • notification-setting 동기화 로직 개선

🧪 테스트 내역

  • 주요 기능 정상 동작 확인
  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

💬 추가 설명 or 리뷰 포인트

  • 기존에는 FCM 토큰을 로컬에 저장해 비교한 뒤, 값이 동일하면 서버 동기화를 생략하는 방식으로 동작했습니다. 그러나 이 방식은 서버의 기기 등록 상태와 불일치가 발생할 가능성이 있어, 비교 조건을 제거하고 앱 진입 시마다 서버에 토큰을 재등록하도록 동작을 수정했습니다.
  • 파멸적이었던 notification-setting 동기화 조건문을 조금 더 직관적으로 변경했습니다.
    • 최초 동기화(lastSynced == null)인 경우
    • 알림 수신에 대한 유효 상태(effective = permissionGranted && userEnabled)와 마지막으로 서버에 동기화된 값이 다른 경우
  • HomePresenter와 NotificationPresenter 양쪽에서 동일한 notification-setting 동기화 로직을 사용하고 있어 유틸 함수로 추출했습니다.

Summary by CodeRabbit

릴리즈 노트

  • 개선 사항

    • 알림 동기화 판단이 더 정확해져 불필요한 동기화가 줄어듭니다.
    • 권한 및 사용자 설정을 통합한 "실제 활성화" 기준으로 동작합니다.
  • 리팩토링

    • 기기 토큰 관리 흐름이 단순화되어 내부 처리 부담이 감소했습니다.
    • 알림 설정 관련 코드 구조가 정리되어 안정성과 유지보수성이 향상되었습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 14, 2025

Walkthrough

알림 동기화 판단 로직을 분리한 유틸 함수 추가 및 FCM 토큰의 로컬 저장/비교 API 제거; 리포지토리에서 로컬 토큰 비교/저장 로직을 삭제하고 프레젠터에서 "효과적인 알림 활성화"를 계산해 새로운 유틸을 사용하도록 변경했습니다.

Changes

응집단 / 파일(들) 변경 요약
유틸 추가
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/NotificationSyncUtils.kt
shouldSyncNotification(effectiveEnabled: Boolean, lastSynced: Boolean?): Boolean 함수 추가 (lastSynced이 null이거나 값이 다르면 true 반환).
FCM 토큰 데이터소스 제거
core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/NotificationDataSource.kt, core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt
val fcmToken: Flow<String>suspend fun setFcmToken(fcmToken: String) 제거; 관련 데이터스토어 키(FCM_TOKEN) 제거.
리포지토리 동기화 로직 변경
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt
로컬 FCM 토큰 조회/비교 및 로컬 저장 로직 제거; 등록 요청은 항상 실행되도록 변경(토큰을 로컬에 저장하지 않음).
프레젠터: 효과적 활성화 사용
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt, feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
사용자 설정과 권한을 조합한 effectiveNotificationEnabled 계산 도입; 기존 inline 비교를 shouldSyncNotification(effectiveNotificationEnabled, lastSyncedServerEnabled) 호출로 대체; 동기화 호출에 effectiveNotificationEnabled 전달.

Sequence Diagram(s)

sequenceDiagram
    participant Presenter
    participant Repo as DefaultUserRepository
    participant Server

    rect rgba(200,240,200,0.3)
    Presenter->>Presenter: userSettingEnabled & isPermissionGranted\n=> effectiveNotificationEnabled
    Presenter->>Presenter: shouldSyncNotification(effective, lastSynced)
    end

    alt shouldSync == true
        Presenter->>Repo: syncNotificationSettings(effectiveNotificationEnabled)
        Repo->>Server: register/update notification settings (always send)
        Server-->>Repo: 200 OK
        Repo-->>Presenter: result
    else shouldSync == false
        Presenter-->>Presenter: no network sync
    end
Loading

(다른 변경—FCM 토큰 관련 데이터소스 제거와 리포지토리 내부 토큰 비교 삭제—은 제어 흐름을 단순화하여 "항상 서버에 동기화" 경로로 통일됨.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 추가 검토 필요 사항:
    • NotificationDataSource API 제거가 다른 모듈/사용 지점에 미치는 영향(컴파일 오류 또는 미사용 코드) 확인
    • 리포지토리의 syncFcmToken 변경으로 인한 서버 요청 빈도 및 실패 처리(재시도, 중복 등록) 검토
    • presenters에서 계산된 effectiveNotificationEnabled 논리(권한 및 사용자 설정 병합)가 모든 시나리오에서 기대대로 작동하는지 확인

🐰 토큰은 가볍게, 판단은 똑똑하게
사용자의 뜻과 허가를 함께 보며,
서버로 날려 보내 흐름을 맞추네 —
깡총깡총, 동기화는 이제 심플하게! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. 'FCM 토큰 및 알림 설정 동기화 로직 개선'은 로컬 FCM 토큰 비교 로직 제거, 서버 동기화 방식 변경, DataStore 정리를 포함한 전체 변경의 핵심을 적절히 나타냅니다.
Linked Issues check ✅ Passed PR의 모든 변경사항이 연결된 이슈 #225의 목표를 충족합니다. 로컬 FCM 토큰 비교 로직 제거 [#225], FCM 관련 DataStore 코드 정리 [#225], shouldSyncNotification 유틸리티 함수 추가를 통한 알림 동기화 로직 개선이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #225의 목표범위 내에 있습니다. NotificationSyncUtils의 새로운 유틸리티 함수, DefaultUserRepository의 로컬 토큰 처리 제거, DataStore의 FCM 토큰 API 제거, Presenter들의 로직 개선은 모두 FCM 동기화 방식 변경과 DataStore 정리 목표에 직접 관련됩니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-437-refactor/#225

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74ea145 and f54d6e0.

📒 Files selected for processing (1)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (0 hunks)
💤 Files with no reviewable changes (1)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Compose Stability Check
  • GitHub Check: ci-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56bc9d2 and 74ea145.

📒 Files selected for processing (6)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/NotificationSyncUtils.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (1 hunks)
  • core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/NotificationDataSource.kt (0 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt (0 hunks)
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (2 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt (2 hunks)
💤 Files with no reviewable changes (2)
  • core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/NotificationDataSource.kt
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
📚 Learning: 2025-08-28T12:25:54.058Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 174
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:128-133
Timestamp: 2025-08-28T12:25:54.058Z
Learning: In BookSearchPresenter.kt, when a guest user tries to register a book and is redirected to login, the bottom sheet (isBookRegisterBottomSheetVisible) and selection state (selectedBookIsbn, selectedBookStatus) are intentionally kept open/preserved so that when the user returns from login, they can continue from where they left off without losing context.

Applied to files:

  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
📚 Learning: 2025-10-17T08:03:00.309Z
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 192
File: feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt:74-76
Timestamp: 2025-10-17T08:03:00.309Z
Learning: Android 13 이상에서는 POST_NOTIFICATIONS 런타임 권한과 시스템 알림 설정(areNotificationsEnabled)이 완전히 동기화되어 있음. 하나의 토글로 둘 다 함께 변경되므로, checkSelfPermission() 체크만으로 충분함. Android 12 이하에서는 런타임 권한이 없으므로 areNotificationsEnabled()만 사용해야 함.

Applied to files:

  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
🧬 Code graph analysis (2)
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/NotificationSyncUtils.kt (1)
  • shouldSyncNotification (3-4)
feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt (1)
  • syncNotificationSettings (62-81)
feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/NotificationSyncUtils.kt (1)
  • shouldSyncNotification (3-4)
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (1)
  • syncNotificationSettings (77-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci-build
  • GitHub Check: Compose Stability Check
🔇 Additional comments (4)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/NotificationSyncUtils.kt (1)

3-4: 간결하고 명확한 유틸리티 함수입니다.

초기 동기화(lastSynced == null)와 상태 변경(lastSynced != effectiveEnabled) 케이스를 모두 올바르게 처리하고 있으며, HomePresenter와 NotificationPresenter에서 중복되던 로직을 효과적으로 추출했습니다.

feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt (1)

93-107: 알림 설정 동기화 로직 개선이 잘 되었습니다.

userSettingEnabledisPermissionGranted를 결합한 effectiveNotificationEnabled 값을 사용하여 실제 알림 상태를 서버에 동기화하는 방식이 직관적이고 정확합니다. 공통 유틸리티 함수를 사용하여 HomePresenter와의 일관성도 확보되었습니다.

feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (1)

116-130: NotificationPresenter와 일관된 동기화 로직 개선입니다.

동일한 패턴으로 리팩토링되어 코드 일관성이 확보되었으며, 공통 유틸리티 함수를 사용하여 중복 로직을 제거했습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (1)

41-48: 동기화 호출 패턴 확인 완료 - 설계가 의도적이고 타당합니다.

검증 결과, syncFcmToken()은 다음 3곳에서 호출됩니다:

  • 앱 시작 시 (SplashPresenter:56) - 사용자 프로필 로드 후 매번 호출
  • 로그인 후 (LoginPresenter:93) - 로그인 성공 시 호출
  • FCM 토큰 갱신 시 (ReedFirebaseMessagingService:35) - Firebase에서 새 토큰 발급 시 호출

앱 시작 시마다 네트워크 호출이 발생하는 것은 맞으나, 이는 기기 등록 불일치를 방지하기 위한 의도적인 설계입니다. 호출 패턴이 적절하고 필요한 생명주기에서만 동기화되므로 문제 없습니다.

@easyhooon
Copy link
Copy Markdown
Contributor

그러나 이 방식은 서버의 기기 등록 상태와 불일치가 발생할 가능성이 있어

혹시 불일치가 발생할 엣지케이스가 어떤 경우일까요? 어떤 흐름으로 흘러갔을때 발생할수있을지 정리해보면 좋을것같군요.
현재 변경해주신 방법이 심플해서 좋은데 이전 방식도 앱 단에서 최대한 최적화를 하려고 노력하는게 보였어서 나쁘지않았습니다!

Copy link
Copy Markdown
Contributor

@easyhooon easyhooon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

@seoyoon513
Copy link
Copy Markdown
Contributor Author

혹시 불일치가 발생할 엣지케이스가 어떤 경우일까요? 어떤 흐름으로 흘러갔을때 발생할수있을지 정리해보면 좋을것같군요. 현재 변경해주신 방법이 심플해서 좋은데 이전 방식도 앱 단에서 최대한 최적화를 하려고 노력하는게 보였어서 나쁘지않았습니다!

이런 경우가 흔치 않겠지만, 서버에서 deviceId–fcmToken 정보가 삭제될 경우 클라이언트에서 대응할 수 있는 방법이 없다는 점이 가장 큰 리스크인 것 같아요. 기존 구조는 클라이언트가 로컬에 저장된 값을 기준으로 동작하기 때문에 한 번 등록에 성공하면 앱 재설치나 FCM 토큰 만료 같은 이벤트가 발생하지 않는 이상 서버에 재등록을 시도하지 않아요. 그 결과 서버와 상태가 어긋나더라도 다시 맞출 계기가 없어지고, 이런 불일치는 발생 빈도가 낮아도 한 번 발생하면 복구하기 어려운 치명적인 버그라고 생각했습니다🥲

또 이번 배포처럼 충분한 엣지 케이스를 테스트하기 어려운 상황에서는 비용이 발생하더라도 매번 upsert하는 방식이 안전하다는 판단도 있었습니다. 향후 시스템이 안정화되면 다시 로컬 데이터를 기반으로 판단하거나 서버에서 내려주는 값을 참고하는 구조로 전환하는 것도 좋을 것 같아요

@seoyoon513 seoyoon513 merged commit 0b5e477 into develop Nov 15, 2025
4 checks passed
@seoyoon513 seoyoon513 deleted the BOOK-437-refactor/#225 branch November 15, 2025 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-437/refactor] FCM 토큰 동기화 방식 변경

2 participants