user15053529
user15053529

Reputation: 13

Add additional logic to chain of publisher transformation without affecting Publisher type

Kotlin’s coroutines provide the ability to write very flat code. I am trying to work on converting some of the async iOS code to utilize Combine to flatten it out on my end as well.

Kotlin looks like:

private suspend fun getData(
        networkCall: Boolean = true,
        networkRequest: suspend () -> Either<FieldError, List<JobModel>>,
        localRequest: suspend () -> Either<FieldError, List<JobModel>>,
        cache: suspend (data: List<JobModel>) -> Unit
): Either<FieldError, List<JobModel>> {
    // getting of Jobs and passing in pair if result was from network
    var resultNetwork = false
    var result: Either<FieldError, List<JobModel>> = Left(GenericError)
    if (!networkCall) {
        result = localRequest()
    }
    // if it was not a network call and failed we will force network call
    if (networkCall || result.isLeft()) {
        resultNetwork = true
        result = networkRequest()
    }

    if (result is Either.Right && resultNetwork) {
        cache(result.b)
    }

    return result

}

Swift WIP looks like:

public func getData(isNetworkCall: AnyPublisher<Bool, Error>,
                    networkRequest: AnyPublisher<[Job], Error>,
                    localRequest: AnyPublisher<[Job], Error>,
                    cache: ([Job]) -> AnyPublisher<Void, Error>) -> AnyPublisher<[Job], Error>? {
    
    let getJobsRequest =  isNetworkCall.flatMap { (isCall) in
        return isCall
            ? networkRequest
            //.also { jobs in cache(jobs) }
            .catch{ _ in return localRequest }
            .eraseToAnyPublisher()
            : localRequest
    }.eraseToAnyPublisher()
    
    return getJobsRequest
}

How do I add this logic of caching data as part of this AnyPublisher? I would like to have it cache in the midst of this logic transformation. Ideally there could be an also function that appends logic when the transaction is being completed by the subscriber.

Solution:

private func getData(isNetworkCall: AnyPublisher<Bool, Error>,
                         networkRequest: AnyPublisher<[Job], Error>,
                         localRequest: AnyPublisher<[Job], Error>,
                         cache: @escaping ([Job]) -> AnyPublisher<Void, Error>) -> AnyPublisher<[Job], Error> {
    
    // Sequence of steps for when we should do a network call
    let networkCallFlow = networkRequest
        .flatMap { jobs in // cache jobs from network
            cache(jobs)
                .replaceError(with: ()) // fire and forget cache, replace error with Void to continue
                .map { _ in jobs } // need to give back jobs to flatMap
                .setFailureType(to: Error.self) // match failure type
        }
        .catch { _ in localRequest } // return local if network fails
        .eraseToAnyPublisher()
    
    // Sequence of steps for when we should get from local
    let localCallFlow = localRequest
        .catch { _ in networkCallFlow } // do network flow if local call fails
        .eraseToAnyPublisher()

    return isNetworkCall
        .flatMap { $0 ? networkCallFlow : localCallFlow }
        .eraseToAnyPublisher()
}

Upvotes: 1

Views: 170

Answers (1)

New Dev
New Dev

Reputation: 49590

You can chain it with flatMap.

It would have been easier if cache was also a AnyPublisher<[Job], Error>, then you could have had the following:

return isCall
    ? networkRequest
        .flatMap { jobs in cache(jobs) }
        .catch{ _ in return localRequest }
        .eraseToAnyPublisher()
    : localRequest

Otherwise, you'd need to map its Void returned value back to jobs:

return isCall
    ? networkRequest
        .flatMap { jobs in 
            cache(jobs).map { _ in jobs }
        }
        .catch{ _ in return localRequest }
        .eraseToAnyPublisher()
    : localRequest

Upvotes: 1

Related Questions