How can execute operations sequentially and update UI

Viewed 118

I'm trying to execute a bunch of operations sequentially and update my UI every time an operation it starts and it finishes (I have to update an operation status icon color).

Below my (working) code:

class SyncManager {

private let disposeBag = DisposeBag()

// MARK: - Private Init

private init() { }

// MARK: - Public Constants

static let shared = SyncManager()

let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)

func startDatabaseSync(completion: @escaping ((Result<Void, Error>) -> Void)) {
    refreshTokenStatus.accept(.todo)
    updateCatalogDataStatus.accept(.todo)
    insertDataStatus.accept(.todo)
    getDataStatus.accept(.todo)
    RefreshTokenManager.shared.refreshToken().do(onSuccess: { [self]_ in
        print("RefreshTokenManager onSuccess")
        refreshTokenStatus.accept(.completed)
    }, onError: { [self] error in
        print("RefreshTokenManager onError: \(error)")
        refreshTokenStatus.accept(.error)
    }, onSubscribe: { [self] in
        print("RefreshTokenManager onSubscribe")
        refreshTokenStatus.accept(.running)
    }).asObservable().concatMap { result in
        UpdateCatalogDataSyncManager.shared.updateCatalogData().do(onSuccess: { [self] in
            print("UpdateCatalogDataSyncManager onSuccess")
            updateCatalogDataStatus.accept(.completed)
        }, onError: { [self] error in
            print("UpdateCatalogDataSyncManager onError: \(error)")
            updateCatalogDataStatus.accept(.error)
        }, onSubscribe: { [self] in
            print("UpdateCatalogDataSyncManager onSubscribe")
            updateCatalogDataStatus.accept(.running)
        }).asObservable().concatMap { result in
            GetDataSyncManager.shared.getData().do { [self] in
                print("GetDataSyncManager onSuccess")
                getDataStatus.accept(.completed)
            } onError: { [self] error in
                print("GetDataSyncManager onError: \(error)")
                getDataStatus.accept(.error)
            } onSubscribe: { [self] in
                print("GetDataSyncManager onSubscribe")
                getDataStatus.accept(.running)
            } onDispose: {
                print("GetDataSyncManager onDispose")
            }.asObservable().concatMap { _ in
                InsertDataWorkSyncManager.shared.insertData().do { [self] in
                    print("InsertDataWorkSyncManager onSuccess")
                    insertDataStatus.accept(.completed)
                }  onError: { [self] error in
                    print("InsertDataWorkSyncManager onError: \(error)")
                    insertDataStatus.accept(.error)
                } onSubscribe: { [self] in
                    print("InsertDataWorkSyncManager onSubscribe")
                    insertDataStatus.accept(.running)
                } onDispose: {
                    print("InsertDataWorkSyncManager onDispose")
                }

            }
        }
    }.subscribe { _ in
        print("SyncManager onNext")
    } onError: { error in
        print("SyncManager onError: \(error)")
        completion(.failure(error))
    } onCompleted: {
        print("SyncManager onCompleted")
        completion(.success(()))
    } onDisposed: {
        print("SyncManager onDisposed")
    }.disposed(by: disposeBag)
}
}

enum SyncStatus {
    
    case todo
    case completed
    case error
    case running
    case partial
    
}

My ViewController:

SyncManager.shared.refreshTokenStatus.skip(1).subscribe(onNext: { status in
        // Update UI
    }).disposed(by: disposeBag)
    SyncManager.shared.updateCatalogDataStatus.skip(1).subscribe(onNext: { status in
        // Update UI
    }).disposed(by: disposeBag)
    SyncManager.shared.insertDataStatus.skip(1).subscribe(onNext: { status in
        // Update UI
    }).disposed(by: disposeBag)

I'm new to RxSwift (I've been using it for only a week) so I would like to know if there's a better approach to achieve my above goal.

1 Answers

Here is an idea that I think will work. It is very imperative conceptually which makes it hard to transcribe to the functional declarative paradigm of FRP. I kept the same external interface so it could be a drop in replacement.

class SyncManager {
    private init() { }
    static let shared = SyncManager()
    
    let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
    let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
    let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
    let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
    
    private let disposeBag = DisposeBag()
    
    func startDatabaseSync(completion: @escaping (Result<Void, Error>) -> Void) {
        let sync = Sync.startDatabaseSync()
        disposeBag.insert(
            sync.refreshTokenStatus.bind(to: refreshTokenStatus),
            sync.updateCatalogDataStatus.bind(to: updateCatalogDataStatus),
            sync.insertDataStatus.bind(to: insertDataStatus),
            sync.getDataStatus.bind(to: getDataStatus),
            sync.getDataStatus.subscribe(
                onError: { error in
                    completion(.failure(error))
                },
                onCompleted: {
                    completion(.success(()))
                }
            )
        )
    }
}

struct Sync {
    let refreshTokenStatus: Observable<SyncStatus>
    let updateCatalogDataStatus: Observable<SyncStatus>
    let getDataStatus: Observable<SyncStatus>
    let insertDataStatus: Observable<SyncStatus>
    
    static func startDatabaseSync() -> Sync {
        let refreshTokenStatus = handle(RefreshTokenManager.shared.refreshToken(), after: .just(.completed))
            .catchAndReturn(.error)
        let updateCatalogDataStatus = handle(UpdateCatalogDataSyncManager.shared.updateCatalogData(), after: refreshTokenStatus)
            .catchAndReturn(.error)
        let getDataStatus = handle(GetDataSyncManager.shared.getData(), after: updateCatalogDataStatus)
            .catchAndReturn(.error)
        let insertDataStatus = handle(InsertDataWorkSyncManager.shared.insertData(), after: getDataStatus)
            .catchAndReturn(.error)
        
        return Sync(
            refreshTokenStatus: refreshTokenStatus,
            updateCatalogDataStatus: updateCatalogDataStatus,
            getDataStatus: getDataStatus,
            insertDataStatus: insertDataStatus
        )
    }
}

func handle(_ operation: Single<Void>, after: Observable<SyncStatus>) -> Observable<SyncStatus> {
    after
        .ignoreElements()
        .asCompletable()
        .andThen(
            operation
                .map { SyncStatus.completed }
                .asObservable()
                .startWith(SyncStatus.running)
        )
        .startWith(.todo)
}

enum SyncStatus {
    case todo
    case completed
    case error
    case running
}

If you were to re-arrange the rest of the code to be more in the Rx style, then you could probably make this much cleaner...

Related