How do I bind an Rx Bool to selectedSegmentIndex of a SegmentedController? - ios

I have an rx observable returning a Bool that I need to bind to a SegmentedControl's selectedSegmentIndex. The function looks like this:
func getLockPower() -> Observable<Bool> {
do {
return try doorModeService.getDoorLockPower().map { $0.rawValue == 1 }.asObservable()
} catch {
return .error(error)
}
}
And I have tried binding it to my SegementedControl like this:
lockViewModel.getLockPower()
.bind(to: lockInstalledSegmentController.rx.selectedSegmentIndex)
.disposed(by: disposeBag)
But I get the error:
No exact matches in call to instance method 'bind'
I don't think I get the syntax, and I haven't been able to find any source on how it should look like. Any suggestions?

Alright, I worked it out. The index obviously need an Int, so instead of mapping to Bool I mapped to an Int and it worked.

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.

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)

rxSwift extension for converting Single<[Element]> to Observable<Element>

I'm having a real hard time trying to create an extension that converts a Single array to an Observable. So for example I have a Single<[Address]> and I want to convert it to an Observable<Address>.
The code I have to write each time I want to do this is
mySingleVariable.asObservable().flatMap({ addresses in Observable.from(addresses) })
This operation is very common and the code is quite verbose. My attempt to create an extension looks like the following
extension PrimitiveSequence where Trait == SingleTrait {
func flatObservable<R: Collection>() -> Observable<R.Element> {
return asObservable().flatMap({ element in Observable.from(element) })
}
}
The code above unfortunately does not work. The error I get is "Generic parameter 'R' is not used in function signature". This is because the function returns Observable<R.Element>. If it were to return Observable<R>, the error would go away, but it's not the result I'm trying to achieve.
Try this:
extension PrimitiveSequence where Trait == SingleTrait, Element: Sequence {
func flatObservable<R>() -> Observable<R> where R == Element.Element {
return asObservable().flatMap { Observable.from($0) }
}
}

RxSwift: Chain Completable to Observable

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

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