-
Notifications
You must be signed in to change notification settings - Fork 0
[#284] 탭뷰에 있는 알림 이미지에 읽지 않은 푸시알림 데이터가 있을 경우 해당 데이터의 갯수를 배지로 보이도록 구현한다 #290
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 4 commits
2caaa26
271f21b
1fa9d72
7e472ed
b0fea8a
38cd53f
98dd93f
5fa5a1e
5f11c56
27fb444
21073cd
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 |
|---|---|---|
|
|
@@ -17,7 +17,9 @@ struct RootView: View { | |
| Color(UIColor.systemGroupedBackground).ignoresSafeArea() | ||
| if let signIn = viewModel.state.signIn { | ||
| if signIn && !viewModel.state.isFirstLaunch { | ||
| MainView() | ||
| MainView(viewModel: MainViewModel( | ||
| observeUnreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self) | ||
| )) | ||
| } else { | ||
|
Comment on lines
19
to
23
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.
Badge updates now only happen through Useful? React with 👍 / 👎. |
||
| LoginView(viewModel: LoginViewModel( | ||
| signInUseCase: container.resolve(SignInUseCase.self), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // | ||
| // ObserveUnreadPushCountUseCase.swift | ||
| // DevLog | ||
| // | ||
| // Created by opfic on 3/17/26. | ||
| // | ||
|
|
||
| import Combine | ||
|
|
||
| protocol ObserveUnreadPushCountUseCase { | ||
| func execute() throws -> AnyPublisher<Int, Error> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // | ||
| // ObserveUnreadPushCountUseCaseImpl.swift | ||
| // DevLog | ||
| // | ||
| // Created by opfic on 3/17/26. | ||
| // | ||
|
|
||
| import Combine | ||
|
|
||
| final class ObserveUnreadPushCountUseCaseImpl: ObserveUnreadPushCountUseCase { | ||
| private let repository: PushNotificationRepository | ||
|
|
||
| init(_ repository: PushNotificationRepository) { | ||
| self.repository = repository | ||
| } | ||
|
|
||
| func execute() throws -> AnyPublisher<Int, Error> { | ||
| try repository.observeUnreadPushCount() | ||
| .removeDuplicates() | ||
| .eraseToAnyPublisher() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // | ||
| // MainViewModel.swift | ||
| // DevLog | ||
| // | ||
| // Created by opfic on 3/17/26. | ||
| // | ||
|
|
||
| import Foundation | ||
| import Combine | ||
| import UserNotifications | ||
|
|
||
| @Observable | ||
| final class MainViewModel: Store { | ||
| struct State: Equatable { | ||
| var unreadPushCount = 0 | ||
| var showAlert = false | ||
| var alertTitle = "" | ||
| var alertMessage = "" | ||
| } | ||
|
|
||
| enum Action { | ||
| case setUnreadPushCount(Int) | ||
| case setAlert(Bool) | ||
| } | ||
|
|
||
| enum SideEffect { | ||
| case updateBadgeCount(Int) | ||
| } | ||
|
|
||
| private(set) var state = State() | ||
| private var cancellables = Set<AnyCancellable>() | ||
| private let observeUnreadPushCountUseCase: ObserveUnreadPushCountUseCase | ||
|
|
||
| init( | ||
| observeUnreadPushCountUseCase: ObserveUnreadPushCountUseCase | ||
| ) { | ||
| self.observeUnreadPushCountUseCase = observeUnreadPushCountUseCase | ||
| observeUnreadPushCount() | ||
| } | ||
|
Comment on lines
+38
to
+42
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 reduce(with action: Action) -> [SideEffect] { | ||
| var state = self.state | ||
| var sideEffects: [SideEffect] = [] | ||
|
|
||
| switch action { | ||
| case .setUnreadPushCount(let count): | ||
| state.unreadPushCount = count | ||
| sideEffects = [.updateBadgeCount(count)] | ||
| case .setAlert(let isPresented): | ||
| setAlert(&state, isPresented: isPresented) | ||
| } | ||
|
|
||
| if self.state != state { self.state = state } | ||
| return sideEffects | ||
| } | ||
|
|
||
| func run(_ effect: SideEffect) { | ||
| switch effect { | ||
| case .updateBadgeCount(let count): | ||
| updateBadgeCount(count) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private extension MainViewModel { | ||
| func setAlert( | ||
| _ state: inout State, | ||
| isPresented: Bool | ||
| ) { | ||
| state.alertTitle = "오류" | ||
| state.alertMessage = "알림 배지를 불러오는 중 문제가 발생했습니다." | ||
| state.showAlert = isPresented | ||
| } | ||
|
|
||
| func observeUnreadPushCount() { | ||
| do { | ||
| try observeUnreadPushCountUseCase.execute() | ||
| .receive(on: DispatchQueue.main) | ||
| .sink( | ||
| receiveCompletion: { [weak self] completion in | ||
| guard let self else { return } | ||
| if case .failure = completion { | ||
| self.send(.setAlert(true)) | ||
| } | ||
| }, | ||
| receiveValue: { [weak self] count in | ||
| self?.send(.setUnreadPushCount(count)) | ||
| } | ||
| ) | ||
| .store(in: &cancellables) | ||
| } catch { | ||
| send(.setAlert(true)) | ||
| } | ||
| } | ||
|
|
||
| func updateBadgeCount(_ count: Int) { | ||
| UNUserNotificationCenter.current().setBadgeCount(count) { _ in } | ||
| } | ||
|
Comment on lines
+108
to
+114
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.
// In MainViewModel
private let logger = Logger(category: "MainViewModel")
// ...
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)
}
}
} |
||
| } | ||
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.
RootView의body에서MainViewModel(...)를 직접 만들면RootViewModel상태 변경(세션/테마/네트워크 등)마다 새 인스턴스가 계속 생성됩니다.MainViewModel.init이 즉시observeUnreadPushCount()를 실행해 Firestore 구독을 시작하므로, 실제로는MainView의@State에 채택되지 않고 버려지는 인스턴스에서도 불필요한 리스너 등록/해제가 반복되어 성능과 네트워크 비용이 증가합니다.Useful? React with 👍 / 👎.