-
Notifications
You must be signed in to change notification settings - Fork 0
[#270 푸시 알람 배지가 앱 아이콘에 뜨도록 구현한다 #271
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,15 @@ import FirebaseAuth | |
| import FirebaseFirestore | ||
| import FirebaseMessaging | ||
| import GoogleSignIn | ||
| import Combine | ||
| import UserNotifications | ||
|
|
||
| class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate { | ||
| private let logger = Logger(category: "AppDelegate") | ||
| private var store: Firestore { Firestore.firestore() } | ||
| private var authStateListenerHandle: AuthStateDidChangeListenerHandle? | ||
| private var cancellable: AnyCancellable? | ||
|
|
||
| func application( | ||
| _ app: UIApplication, | ||
| open url: URL, | ||
|
|
@@ -47,6 +52,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate { | |
|
|
||
| // Firebase Messaging 설정 | ||
| Messaging.messaging().delegate = self | ||
| observeAuthState() | ||
|
|
||
| // 앱이 완전 종료되어도, 알림을 통해 앱이 시작된 경우 처리 | ||
| if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] { | ||
|
|
@@ -73,6 +79,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate { | |
| ) { | ||
| logger.error("Failed to register APNs token", error: error) | ||
| } | ||
|
|
||
| func applicationDidBecomeActive(_ application: UIApplication) { | ||
| syncBadgeCount() | ||
| } | ||
|
|
||
| // FCMToken 갱신 | ||
| func messaging( | ||
|
|
@@ -83,8 +93,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate { | |
| NotificationCenter.default.post(name: .fcmToken, object: nil, userInfo: ["fcmToken": fcmToken]) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func updateUserTimeZone() { | ||
| private extension AppDelegate { | ||
| func updateUserTimeZone() { | ||
| Task { | ||
| do { | ||
| guard let uid = Auth.auth().currentUser?.uid else { return } | ||
|
|
@@ -96,6 +108,117 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate { | |
| } | ||
| } | ||
| } | ||
|
|
||
| func observeAuthState() { | ||
| authStateListenerHandle = Auth.auth().addStateDidChangeListener { [weak self] _, user in | ||
| guard let self else { return } | ||
|
|
||
| self.cancellable?.cancel() | ||
|
|
||
| guard user != nil else { | ||
| self.updateBadgeCount(0) | ||
| return | ||
| } | ||
|
|
||
| self.startObservingBadgeCount() | ||
| self.syncBadgeCount() | ||
| } | ||
| } | ||
|
|
||
| func startObservingBadgeCount() { | ||
| cancellable = try? observeUnreadNotificationCount() | ||
| .receive(on: DispatchQueue.main) | ||
| .sink( | ||
| receiveCompletion: { [weak self] completion in | ||
| guard let self else { return } | ||
|
|
||
| if case .failure(let error) = completion { | ||
| self.logger.error("Failed to observe unread notification count", error: error) | ||
| } | ||
| }, | ||
| receiveValue: { [weak self] count in | ||
| self?.updateBadgeCount(count) | ||
| } | ||
| ) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
func startObservingBadgeCount() {
do {
cancellable = try observeUnreadNotificationCount()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] completion in
guard let self else { return }
if case .failure(let error) = completion {
self.logger.error("Failed to observe unread notification count", error: error)
}
},
receiveValue: { [weak self] count in
self?.updateBadgeCount(count)
}
)
} catch {
logger.error("Failed to start observing badge count", error: error)
}
} |
||
|
|
||
| func fetchUnreadNotificationCount() async throws -> Int { | ||
| logger.info("Fetching unread notification count") | ||
|
|
||
| guard let uid = Auth.auth().currentUser?.uid else { | ||
| logger.error("User not authenticated") | ||
| throw AuthError.notAuthenticated | ||
| } | ||
|
|
||
| do { | ||
| let snapshot = try await store.collection("users/\(uid)/notifications") | ||
| .whereField("isRead", isEqualTo: false) | ||
| .getDocuments() | ||
|
|
||
| let unreadNotificationCount = snapshot.documents.count | ||
| logger.info("Unread notification count: \(unreadNotificationCount)") | ||
| return unreadNotificationCount | ||
| } catch { | ||
| logger.error("Failed to fetch unread notification count", error: error) | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
| func observeUnreadNotificationCount() throws -> AnyPublisher<Int, Error> { | ||
| logger.info("Observing unread notification count") | ||
|
|
||
| guard let uid = Auth.auth().currentUser?.uid else { | ||
| logger.error("User not authenticated") | ||
| throw AuthError.notAuthenticated | ||
| } | ||
|
|
||
| let subject = PassthroughSubject<Int, Error>() | ||
| let listener = store.collection("users/\(uid)/notifications") | ||
| .whereField("isRead", isEqualTo: false) | ||
| .addSnapshotListener { [weak self] snapshot, error in | ||
| if let error { | ||
| self?.logger.error("Failed to observe unread notification count", error: error) | ||
| subject.send(completion: .failure(error)) | ||
| return | ||
| } | ||
|
|
||
| guard let snapshot else { return } | ||
|
|
||
| let unreadNotificationCount = snapshot.documents.count | ||
| self?.logger.info("Observed unread notification count: \(unreadNotificationCount)") | ||
| subject.send(unreadNotificationCount) | ||
| } | ||
|
Comment on lines
+199
to
+212
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
.addSnapshotListener { [weak self] snapshot, error in
guard let self else { return }
if let error {
self.logger.error("Failed to observe unread notification count", error: error)
subject.send(completion: .failure(error))
return
}
guard let snapshot else { return }
let unreadNotificationCount = snapshot.documents.count
self.logger.info("Observed unread notification count: \(unreadNotificationCount)")
subject.send(unreadNotificationCount)
} |
||
|
|
||
| return subject | ||
| .handleEvents(receiveCancel: { listener.remove() }) | ||
| .eraseToAnyPublisher() | ||
| } | ||
|
|
||
| func syncBadgeCount() { | ||
| Task { @MainActor [weak self] in | ||
| guard let self else { return } | ||
| guard Auth.auth().currentUser != nil else { | ||
| self.updateBadgeCount(0) | ||
| return | ||
| } | ||
|
|
||
| do { | ||
| let unreadNotificationCount = try await self.fetchUnreadNotificationCount() | ||
| self.updateBadgeCount(unreadNotificationCount) | ||
| } catch { | ||
| self.logger.error("Failed to fetch unread notification count", error: error) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @MainActor | ||
| private func updateBadgeCount(_ count: Int) { | ||
| UNUserNotificationCenter.current().setBadgeCount(count) { [weak self] error in | ||
| if let error { | ||
| self?.logger.error("Failed to update badge count", error: error) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension AppDelegate: UNUserNotificationCenterDelegate { | ||
|
|
||
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.
addStateDidChangeListener로 등록한 리스너는 앱이 종료될 때 해제하는 것이 좋습니다.AppDelegate에deinit을 추가하여authStateListenerHandle을 제거하는 것을 고려해보세요. 이렇게 하면 잠재적인 리소스 누수를 방지할 수 있습니다.