Search code examples
swiftconditional-statementscombineflatmap

Swift Combine Conditional FlatMap Results


How can you have different return types for a flatMap when using Swift Combine? I have my first publisher, which emits a value and then I flatMap that to transform it into a new publisher. However, based on the original value, I might need to have a different publisher that returns a different type.

I've added a basic example below.

import Combine


class Testing{
    var subscriptions = Set<AnyCancellable>()
    
    
    func getTestScore()->AnyPublisher<Int, Never>{
        return Just(80).eraseToAnyPublisher()
    }
    
    func congratulate()->AnyPublisher<String, Never>{
        return Just("Good Job!").eraseToAnyPublisher()
    }
    
    func getGPA()->AnyPublisher<Double, Never>{
        return Just(2.2).eraseToAnyPublisher()
    }
    
    init() {
        getTestScore()
            .flatMap{ score in
                if score < 70{
                    return self.getGPA()
                } else{
                    return self.congratulate()
                }
            }
            .sink { _ in } receiveValue: { value in
                print(value)
            }.store(in: &subscriptions)
    }
}

let testing = Testing()


Solution

  • As New Dev said, returning conditional data types is not possible - unless you don't want to erase the type to Any.

    My proposal is to create one dedicated publisher which emits if the score exceeds (or falls below) a certain limit. Now use your new publisher to create two separate, type safe, pipelines. I'm aware that you asked to use just one pipeline. However, I think this approach will give you more benefits.

    Please find my working example below:

    import Combine
    
    class Testing{
        var subscriptions = Set<AnyCancellable>()
        
        func getTestScore()->AnyPublisher<Int, Never> {
            return Just(80).eraseToAnyPublisher()
        }
        
        func congratulate()->AnyPublisher<String, Never> {
            return Just("Good Job!").eraseToAnyPublisher()
        }
        
        func getGPA()->AnyPublisher<Double, Never> {
            return Just(2.2).eraseToAnyPublisher()
        }
        
        init() {
            let scoreExceedsLimit: AnyPublisher<Bool, Never> = getTestScore()
                .map { $0 >= 70 }
                .eraseToAnyPublisher()
            
            scoreExceedsLimit
                .filter { $0 == true }
                .flatMap { _ in self.congratulate() }
                .sink(receiveValue: { value in
                    print("first pipeline: \(value)")
                })
                .store(in: &subscriptions)
            
            scoreExceedsLimit
                .filter { $0 == false }
                .flatMap { _ in self.getGPA() }
                .sink(receiveValue: { value in
                    print("second pipeline: \(value)")
                })
                .store(in: &subscriptions)
        }
    }
    
    let testing = Testing()