Search code examples
iosswiftnotificationscombine

Pass CurrentValueSubject value wrapped in an AnyPublisher to another class


Showing you my code:

protocol SomeProtocol {
    var isLocalNotificationsEnabledPublisher: AnyPublisher<Bool, Error> { get }
    
    func saveLocalNotifications(_ enabled: Bool) throws
}

final class UserSettingsClass {
    private var repository: CoreDataRepo
    
    private var isLocalNotificationsEnabledSubject = CurrentValueSubject<Bool, Error>(false) 
// I make it private so that no other classes can change it from outside,
// I can only subscribe on its values.
    
    private var cancellables = Set<AnyCancellable>()
    
    init(repository: CoreDataRepo) {
        self.repository = repository
        binding()
    }
}

extension UserSettingsClass: SomeProtocol {
    var isLocalNotificationsEnabledPublisher: AnyPublisher<Bool, Error> {
        return isLocalNotificationsEnabledSubject.eraseToAnyPublisher()
    }

    func saveLocalNotifications(_ enabled: Bool) throws {
        var userSettings = userSettings // this is core data object... 
        
        userSettings?.isLocalNotificationsEnabled = enabled 
// this is the important part, it has this toggle.
// I need to be able to send this value to my LocalNotificaitonsService class and based
// on this value understand whether I can send local notifications or not.
        
        if isLocalNotificationsEnabledSubject.value != enabled {
            throw someError
        }
        
        isLocalNotificationsEnabledSubject.send(userSettings?.isLocalNotificationsEnabled ?? false)
    }
}

Here is how I bind it to my publisher so that I could subscribe to the value that I send in the saveLocalNotifications method:

private func binding() {
        isLocalNotificationsEnabledPublisher
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { [weak self] value in
                    do {
                        try saveLocalNotifications(value) // recursion here + not handling error properly...
                    } catch {
                        return
                    }
                }
                
            ).store(in: &cancellables)
    }

This code causes recursion that leads to my app crashing. Here is how I try to use it in my local notifications manager:

private let isLocalNotificationsEnabledPublisher: AnyPublisher<Bool, Error>
    
private var cancellables = Set<AnyCancellable>()
    
public init(isLocalNotificationsEnabledPublisher: AnyPublisher<Bool, Error>) {
        self.isLocalNotificationsEnabledPublisher = isLocalNotificationsEnabledPublisher
    }
extension LocalNotificationsManager: SomeOtherProtocol {
    public func requestNotificationAuthorization() throws {
        isLocalNotificationsEnabledPublisher
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { [weak self] value in
                    if value == true {
                        Task {
                            try await self?.center.requestAuthorization(options: [.badge, .sound, .alert])
                        }
                    }
                }
            ).store(in: &cancellables)
    }

So basically this code does not work properly because I do not understand how to get the subject value in the local notifications manager class. I need to be able to sink on the toggle value from the user settings and decide if I can or can not send local notifications. Thank you in advance.


Solution

  • You have recursion because you call saveLocalNotifications and then that triggers the Combine chain again, since that in turn calls isLocalNotificationsEnabledSubject.send(...).

    To prevent the recursion, you could use removeDuplicates in the Combine chain where you subscribe to isLocalNotificationsEnabledSubject

    That being said, you may wan to reconsider your architecture so that the recursion isn't possible.