I've got a function that will return an observable that will emit an enum based on what stage it is currently on. The enum basically goes like this:
enum ViewModelAction {
case inputEnabled(isEnabled: Bool)
case loadingIndicatorShown(isShown: Bool)
case errorMessageShownIfAvailable(error: Error?)
case loadUIData(with entity: Entity)
case startSession(for entity: Entity)
case endSession(for entity: Entity)
case loadEndUIData(with entity: Entity)
}
Now what I want the function do is like so:
- Function start with input params -> go to 2
- Do:
- Emit disable input from UI
- Emit show loading indicator
- Call API to create session based on input params -> API returns session data -> go to 3
- Do:
If success -> go to 4
If error:
- Emit enable input from UI
- Emit hide loading indicator
- Emit show error with the error -> Complete stream
- Do:
Loop and call API to poll the entity based on the session with the status of READY:
- If success with READY status -> Emit load UI data with entity -> go to 5
- If success with non READY status -> loop again / back to 4
- If error -> Emit show error with the error -> Loop again / back to 4
- Do:
- Call API to start session using the entity -> API returns session data -> go to 6
- Do:
If success:
- Emit enable input from UI
- Emit hide loading indicator
- Emit start session for entity -> Complete stream
If error:
- Emit enable input from UI
- Emit hide loading indicator
- Emit show error with the error -> Complete stream
So in a way, for a successful run, it will emit this exact sequence:
Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: false)),
.just(ViewModelAction.loadingIndicatorShown(isShown: true)),
.just(ViewModelAction.loadUIData(with: entity)),
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.startSession(for: entity)),
.complete()
)
I have actually done this, but I was thinking that it is not that clear and very complicated.
protocol SessionHandlerProtocol {
func create(for userId: String) -> Observable<SessionData>
func start(session: SessionData, entity: Entity) -> Observable<Void>
}
protocol EntityPollerProtocol {
var entity: Observable<Entity?> { get }
func startPolling(for session: SessionData) //Will poll and emit to entity observable
finc stopPolling()
}
class StartSessionStrategy {
private let sessionHandler: SessionHandlerProtocol
private let entityPoller: EntityPollerProtocol
init(sessionHandler: SessionHandlerProtocol,
entityPoller: EntityPollerProtocol) {
self.sessionHandler = sessionHandler
self.entityPoller = entityPoller
}
func handleSession(with userId: String) -> Observable<ViewModelAction> {
let initialUIObservable =
Observable.from(ViewModelAction.inputEnabled(isEnabled: false),
ViewModelAction.loadingIndicatorShown(isShown: true))
let sharedCreateSession =
sessionHandler.create(for: userID).materialize.share()
let createSessionError =
sharedCreateSession
.flatMapLatest {
switch $0 {
case .error(let error):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.errorMessageShownIfAvailable(error: error)),
.complete()
)
default:
return .never()
}
let createSessionSuccess =
sharedCreateSession
.flatMapLatest {
switch $0 {
case .next(let element):
return .just(element)
default:
return .never()
}
let sharedEntityPoller =
createSessionSuccess
.do(onNext: { [weak self] in self?.entityPoller.startPolling(for: $0) })
.withLatestFrom(entityPoller.entity) { return ($0, $1) }
.materialize()
.share()
let entityPollerError =
sharedEntityPoller
.flatMapLatest {
switch $0 {
case .error(let error):
return .just(ViewModelAction.errorMessageShownIfAvailable(error: error))
default:
return .never()
}
let entityPollerSuccessWithReadyStatus =
sharedEntityPoller
.filter { (_, entity) entity.status = .ready }
.flatMapLatest {
switch $0 {
case .next(let element):
return .just(element)
default:
return .never()
}
.do(onNext: { [weak self] _ in self?.stopPolling() })
let doOnEntityPollerSuccessWithReadyStatus =
entityPollerSuccessWithReadyStatus
.map { return ViewModelAction.loadUIData(with: $0.1) }
let sharedStartSession =
entityPollerSuccessWithReadyStatus
.flatMapLatest { [weak self] (session, entity) in
self?.sessionHandler
.start(session: userID, entity: entity)
.map { return (session, entity) }
}.materialize.share()
let startSessionError =
sharedStartSession
.flatMapLatest {
switch $0 {
case .error(let error):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.errorMessageShownIfAvailable(error: error)),
.complete()
)
default:
return .never()
}
let startSessionSuccess =
sharedStartSession
.flatMapLatest {
switch $0 {
case .next(let element):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.startSession(for: element.1)),
.complete
)
default:
return .never()
}
return Observable.merge(
initialUIObservable,
createSessionError,
entityPollerError,
doOnEntityPollerSuccessWithReadyStatus,
startSessionError,
startSessionSuccess
)
}
}
As you can see, the function is pretty big and not that clear. Do you have a suggestion how to refactor this into a cleaner code? Thanks.