RxSwift: Chain Completable to Observable - ios

I'd like to chain a Completable to an observable element. After calling flatMap, onCompleted and onError callbacks don't seem to be called on subscription.
var user = PublishRelay<User>()
func fetchUserInformation(_ userId: String) -> Completable {
return Completable.create { observer in
apiService.fetchInformation(for: userId, completion: { response in
if let name = response?.name {
user.accept(User(name: name))
observer(.completed)
} else {
observer(.error(ServiceError.userInformation))
}
})
return Disposables.create()
}
}
login()
.flatMap{ userId in fetchUserInformation(userId) }
.subscribe(
onCompleted: {
print("Success!") // Not being called at all
},
onError: { error in
print(error) // Not being called at all
}
).disposed(by: disposeBag)
Although fetchUserInformation and observer(.completed) are being called and user information is being successfully fetched, I won't be able to catch onCompleted on subscription (only when preceded by flatMap).
Is there a clean way to achieve this?
Already tried .materialized() just after the flatMap call in order to get an
Observable<Event<Never>>
rather than a
Observable<Never>
It doesn't work either.

The correct solution would be using the ‘andThen’ operator.
someCompletable
.andThen(someObservable)
Edit:
Just read the rest of your code - I'm not sure why you use a Completable at all since it seems you are actually returning some element from that stream.
You'll probably want to use a Single or Plain-ol' observable to relay that value without using an external Relay.

I think that you can do something like this:
login()
.flatMap{ userId -> Observable<Void> in
return fetchUserInformation(userId).andThen(.just(Void()))
}.subscribe(onNext: { _ in
...
}).disposed(by: disposeBag)

As far as I know you can't convert Completable to Observable since the later omits values while Completable does not.
I guess flatMap is returning Observables from Login and then you convert it to Completables and that's why it fails

Related

How do I flatMap two singles?

I have an observable that looks like this:
func getParameters() -> Single<[ParameterModel]?> {
do {
loading.accept(allParameters.isEmpty)
return try parameterService.getParameters()
.do(onSuccess: { [weak self] parameters in
self?.loading.accept(false)
self?.content.accept(parameters?.compactMap { $0 }
.compactMap { TechnicianParameterTableViewCellViewModel(parameter: $0) } ?? [])
})
} catch { return Single.error(error) }
}
The important part is where it compactMaps into a cell. This works as expected. But I also have another observable that looks like this:
func getSingleOrDoubleDoor() -> Single<SingleOrDoubleDoor?> { //SingleOrDoubleDoor is an enum
do {
return try parameterService.getSingleOrDoubleDoor()
} catch {
return Single.error(error)
}
}
Now, I would like to sort of merge getSingleOrDoubleDoor() into getParameters() so that I can access the values of both observables in onSuccess. I want to use the result sort of like this:
.compactMap { TechnicianParameterTableViewCellViewModel(parameter: $0, isSingleOrDouble: $1) } ?? [])
Not being an expert on Rx I still assume that this is done using .flatMap{}. Sort of like:
...return try parameterService.getParameters().flatMap { _ in getSingleOrDoubleDoor() }...
But this gives me an error:
Cannot convert value of type '([ParameterModel]?) -> Single<SingleOrDoubleDoor?>' (aka '(Optional<Array>) -> PrimitiveSequence<SingleTrait, Optional>') to expected argument type '([ParameterModel]?) throws -> PrimitiveSequence<SingleTrait, [ParameterModel]?>'
Tried changing the return expression but it still wouldn't take. Not sure how to make this happen.
Since your methods don't accept parameters, I'll assume they don't depend on each other. If so, you should use the zip method like this:
Single
.zip(getParameters(), getSingleOrDoubleDoor())
.compactMap { TechnicianParameterTableViewCellViewModel(parameter: $0, isSingleOrDouble: $1) } ?? [])
The zip method will trigger the compactMap when both methods return a value. Flatmap has a different purpose, it is usually used when we need to call methods sequentially, meaning the next call needs data from the previous one.

How to resolve two method calls with AnyPublisher return type into one?

I have two methods with identical signature func getItems() -> AnyPublisher<[Item], AppError>.
First is getting items from a storage, and the second one from the internet.
How can I add such logic to the third method with the same return type, if the first method completes successfully I return storage.getItems(), if not I return network.getItems()?
If the publisher returned from storage.getItems() errors out when the items don't exist, then you can "catch" the error and emit a new publisher instead:
func getItems() -> AnyPublisher<[Item], AppError> {
storage.getItems()
.catch { err in
// check the err, if you need to
network.getItems()
}
.eraseToAnyPublisher()
}

Can I flatMap into other streams with completables?

I have several completable rx subscriptions in a stream like this:
viewModel?.setupDoorMode().subscribe(onNext: { shouldConnectToDoor in
if shouldConnectToDoor {
self.viewModel?.connectAndOpenDoor().subscribe(onCompleted: {
self.viewModel?.openOrCloseDoor().subscribe().disposed(by: self.disposeBag)
}).disposed(by: self.disposeBag)
} else {
self.viewModel?.openOrCloseDoor().subscribe().disposed(by: self.disposeBag)
}
}).disposed(by: disposeBag)
I have the feeling that this can be done in a nicer way, like flatMaping the streams into oneanother. But when I try using flatMap I get the error Type '()' cannot conform to 'ObservableConvertibleType'; only struct/enum/class types can conform to protocols. I'm not too familiar with rx to understand that message. Anyway, is there a way to create a more smooth looking stream rather than three subscriptions in a row?
You’re definitely on the right track thinking about flatMap to compose your observables. The thing to note here is that calling .subscribe() returns a Disposable type and calling .disposed(by:) on that disposable returns Void aka () type. You can't compose Voids. You have compose the observables, and THEN you subscribe to the result.
guard let viewModel = viewModel else { return }
// Combine the observables
let doorActionObservable = viewModel
.setupDoorMode()
.flatMap { /* mind the retain cycle */ shouldConnectToDoor in
if shouldConnectToDoor {
return self.viewModel.connectAndOpenDoor()
} else {
return self.viewModel.openOrCloseDoor()
}
}
// Subscribe to the the resulting observable
doorActionObservable
.subscribe()
.disposed(by: disposeBag)

How can I wrap an async function with an Observable

I have an async function that currently looks something like this
func startLoginFlow() {
IdentityProvider.shared.login { success, error in
// on success a user has completed authentication
if success {
delegate?.userIsAuthenticated()
}
// on error something wen't wrong
....
}
}
Essentially on success a delegate method is called and some action takes place as a result.
I'd like to wrap this as an observable instead. I do not have the option refactoring IdentityProvider.shared.login.
I essentially just need the observable to emit so I can subscribe and take action elsewhere using onNext.
I am currently doing the following
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(Void())
}
}
return Disposables.create()
}
}
Is this the best way to do this? I wasn't sure if I should use Observable.of and subscribe to the result of IdentityProvider.shared.login
This is how I create Observables as well. The only thing I would note is to add in the errors so you can handle your observables when it errors out, and the completion, as well, to signal that your observable is complete.
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(())
observable.onCompleted()
} else {
observable.onError(error)
}
}
return Disposables.create()
}
}
Observable.of's work in this case as well. It just emits the completed method. You can test this out yourself, if you were trying to create an Observable<String>, with both methods.
I find that doing Observable.create is beneficial here as you're doing network requests and that you can control how you want your observables to error, fail, or be completed.
Someone here gave a pretty good example as well:
Rxswift What difference between Observable.of and Observable<String>.create

Return a completable in RxSwift without using a create block

I have a Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }

Resources