I'm having array of Bool Observables in Rxswift.
let rxBoolObservableArray: [Observable<Bool>] = [Observable<Bool>]()
Now, How to get If any of the element is false?
func containsFalse(array: [Observable<Bool>]) -> Observable<Bool> {
return Observable.combineLatest(array) { $0.contains(false) }
}
The combineLatest function will subscribe to all the observables in the array.
The above will also update the array every time one of the observables updates its value so the output will always be correct. The accepted answer doesn't do that (it only works for the Observable.just function and is incorrect.)
Here is allSatisfy extension based on #DanielT answer. It might be suitable for your problem:
extension Array where Iterator.Element: ObservableType {
func allSatisfy(_ predicate: #escaping (Iterator.Element.E) throws -> Bool) -> Observable<Bool> {
return Observable.combineLatest(self) { try $0.allSatisfy(predicate) }
}
}
example usage:
rxBoolObservableArray
.allSatisfy { $0 } // { $0 == true }
.subscribe(onNext: { areTestsPassing in
print(areTestsPassing)
})
.disposed(by: disposeBag)
Related
I tried to create a function runsample() that uses multiple observables as below.
If I meet a specific condition in the middle of the stream, I want to start from the beginning of function.
(foo1() in the example below)
In this case, how do I modify the runsample() function?
class SampleClass {
////////////////////////////////
// private
////////////////////////////////
private func foo1() -> Observable<String> {
// Do something
return .just("TEST")
}
private func foo2() -> Observable<Bool> {
// Do something
return .just(false) // or true
}
private func foo3() -> Observable<String> {
// Do something
return .just("Result")
}
////////////////////////////////
// public
////////////////////////////////
public func runSample() -> Observable<String> {
return Observable.just(())
.flatMap { [unowned self] _ in
self.foo1()
}
.flatMap { [unowned self] _ in
self.foo2()
}
// I want to retry foo1() when foo2() is false
// I want to make foo3() run only if foo2() is true.
.flatMap { [unowned self] _ in
self.foo3()
}
}
}
Based on your comment, this is what you want:
func runSample() -> Observable<String> {
struct NotValid: Error { }
return Observable.deferred {
foo1().flatMap { _ in
foo2().do(onNext: { isValid in
if !isValid { throw NotValid() }
})
}
}
.retry()
.flatMap { _ in foo3() }
}
It's a very strange requirement you have, but it's doable. I expect this is an X-Y problem though.
You really want to retry foo1()? That would imply that it failed but it obviously didn't. In any case, this will do what you want:
func runSample() -> Observable<String> {
foo1()
.flatMap { [foo2] _ in
foo2()
}
.flatMap { [foo1, foo3] isTrue in
isTrue ? foo3() : foo1()
}
}
This function will return an Observable. Every time that Observable is subscribed to, the first foo1() will be activated.
Every time the first foo1() emits a value, the value will be ignored (which is quite odd) and foo2() will be called. This will generate a new Observable which will be subscribed to.
Whenever any of the Observables generated by foo2() emit a value, if the value is true foo3() will be called, otherwise foo1() will be called. Whichever one is called, its Observable will be subscribed to.
The entire function will emit all the values that any foo1()s or foo3()s Observables emit.
Importantly for this example, you do not need to start with Observable.just(()).
Thinking about it, I'd prefer something like this:
func runSample() -> Observable<String> {
Observable.zip(foo1(), foo2())
.flatMap { $0.1 ? foo3() : .just($0.0) }
}
That way I don't have to run foo1() twice.
I am trying to get the element of 2 observables produced asynchronously and pass them as parameters to a function once both are received.
However my map operator in my ViewModel below is not executed and the breakpoint just skips over it.
ViewModel.swift
init(api: ApiService) {
self.api = api
}
func getData1() -> Observable<Data1> {
return api.getData1()
}
func getData2() -> Observable<NewViewModel> {
return Observable.create { observer in
let disposable = Disposables.create()
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel( background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
}
In my ViewController i am creating the Zip of the observables because newViewModel[getData2] may return later and i want to call the function when both observables emit a value
in my viewDidLoad() i setup zip by subscribing and adding observables
let zippy = Observable.zip(viewModel.getData1(), viewModel.getData2()).subscribe(onNext: { (data1, newViewModel) in
self.layoutSetUp(data1: data1, newViewModel: newViewModel)
})
zippy.disposed(by: disposeBag)
private func layoutSetUp(data1: Data1, newViewModel: NewViewModel) {
DispatchQueue.main.async {
self.view = SwiftUIHostingView(rootView: SwiftUIContentView(data1: data1, newViewModel: newViewModel))
}
}
This is not executing and no values are passed to function either and im not sure why
Your getData2 method never emits a value so neither will the zip. The code in the method is a bit too muddled for me to understand what you are trying to do so I can't tell you exactly what you need, but I can say that when you have an observable that nothing is subscribed to, then it will not emit a value.
This bit:
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel(background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
Is an observable with no subscribers.
I have an object and its properties as following:
class Section {
var cards: [MemberCard]
init(card: [MemberCard]) {
}
}
class MemberCard {
var name: String
var address: String?
init(name: String) {
self.name = name
}
}
I'm subscribing to a RxStream of type Observable<[Section]>. Before I subscribe I would to want flat map this function.
where the flat map would perform the following actions:
let sectionsStream : Observable<[Section]> = Observable.just([sections])
sectionsStream
.flatMap { [weak self] (sections) -> Observable<[Section]> in
for section in sections {
for card in section.cards {
}
}
}.subscribe(onNext: { [weak self] (sections) in
self?.updateUI(memberSections: sections)
}).disposed(by: disposeBag)
func getAddressFromCache(card: MemberCard) -> Observable<MemberCard> {
return Cache(id: card.name).flatMap ({ (card) -> Observable<MemberCard> in
asyncCall{
return Observable.just(card)
}
}
}
How would the flatmap look like when it comes to converting Observable<[Section]> to array of [Observable<MemberCard>] and back to Observable<[Section]>?
Technically, like that -
let o1: Observable<MemberCard> = ...
let o2: Observable<Section> = omc.toList().map { Section($0) }
let o2: Observable<[Section]> = Observable.concat(o2 /* and all others */).toList()
But I do not think it is an optimal solution, at least because there is no error handling for the case when one or more cards cannot be retrieved. I would rather build something around aggregation with .scan() operator as in https://github.com/maxvol/RaspSwift
Here you go:
extension ObservableType where E == [Section] {
func addressedCards() -> Observable<[Section]> {
return flatMap {
Observable.combineLatest($0.map { getAddresses($0.cards) })
}
.map {
$0.map { Section(cards: $0) }
}
}
}
func getAddresses(_ cards: [MemberCard]) -> Observable<[MemberCard]> {
return Observable.combineLatest(cards
.map {
getAddressFromCache(card: $0)
.catchErrorJustReturn($0)
})
}
If one of the caches emits an error, the above will return the MemberCard unchanged.
I have a couple of other tips as well.
In keeping with the functional nature of Rx, your Section and MemberCard types should either be structs or (classes with lets instead of vars).
Don't use String? unless you have a compelling reason why an empty string ("") is different than a missing string (nil). There's no reason why you should have to check existence and isEmpty every time you want to see if the address has been filled in. (The same goes for arrays and Dictionaries.)
For this code, proper use of combineLatest is the key. It can turn an [Observable<T>] into an Observable<[T]>. Learn other interesting ways of combining Observables here: https://medium.com/#danielt1263/recipes-for-combining-observables-in-rxswift-ec4f8157265f
I need to create some sort of closure to return back if it's an optional or forced update. I've created some pseudo code:
func verifyAppVersionWithServer(isForceUpdate: bool -> true, isOptionalUpdate: bool -> true) {
//Some check will be performed here then:
if isForceUpdate {
return isForceUpdate -> true
} else {
return isOptionalUpdate -> true
}
}
I'm not sure how to create a closure in Swift which will then return which of the parameters is true.
It is probably nicer to return an enum that indicates the type of update required.
You would then have something like this:
enum UpdateType {
case None
case Optional
case Required
}
func verifyAppVersionWithServer(completion:(UpdateType) -> Void) {
let anyUpdate = true
let forcedUpdate = false
if anyUpdate {
if forcedUpdate {
completion(.Required)
} else {
completion(.Optional)
}
} else {
completion(.None)
}
}
You would call it as:
verifyAppVersionWithServer { (updateType) in
print("Update type is \(updateType)")
}
Obviously the values would be determined by your server response, not fixed values as I have shown.
You can use something like below
func verifyAppVersionWithServer(parm1: String, withParma2: Bool, completionHandeler: (isSucess: Bool, error : NSError) -> Void) {
//Write your logic
//call complition handeler
completionHandeler(isSucess: true, error: error)
}
Hope this will help
Currently I have created a function unwrapOptional to safely unwrap the optional input in the stream.
func unwrapOptional<T>(x: Optional<T>) -> Observable<T> {
return x.map(Observable.just) ?? Observable.empty()
}
let aOpt: String? = "aOpt"
_ = Observable.of(aOpt).flatMap(unwrapOptional).subscribeNext { x in print(x)}
let aNil: String? = nil
_ = Observable.of(aNil).flatMap(unwrapOptional).subscribeNext { x in print(x)}
let a: String = "a"
_ = Observable.of(a).flatMap(unwrapOptional).subscribeNext { x in print(x)}
// output
aOpt
a
What I want to archive is to create a handy function instead of using flatMap(unwrapOptional), for example
Observable.of(a).unwrapOptional()
Something I tried to do, but it never compiles...
extension ObservableType {
func unwrapOptional<O : ObservableConvertibleType>() -> RxSwift.Observable<O.E> {
return self.flatMap(unwrapOptional)
}
}
You want the unwrapOptional method to only work on observables that have optional type.
So you somehow have to constraint the Element of Observable to conform to the Optional protocol.
extension Observable where Element: OptionalType {
/// Returns an Observable where the nil values from the original Observable are
/// skipped
func unwrappedOptional() -> Observable<Element.Wrapped> {
return self.filter { $0.asOptional != nil }.map { $0.asOptional! }
}
}
Unfortunately, Swift does not define such a protocol (OptionalType). So you also need to define it yourself
/// Represent an optional value
///
/// This is needed to restrict our Observable extension to Observable that generate
/// .Next events with Optional payload
protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}
/// Implementation of the OptionalType protocol by the Optional type
extension Optional: OptionalType {
var asOptional: Wrapped? { return self }
}
checkout unwrap at https://github.com/RxSwiftCommunity/RxSwift-Ext :)
or https://github.com/RxSwiftCommunity/RxOptional
For now, you should use RxOptional for your personal needs
However, RxSwift-Ext will be growth exponentially in next 2-3 months :)
RxSwift now supports compactMap(). So, now you can do things like:
func unwrap(_ a: Observable<Int?>) -> Observable<Int> {
return a.compactMap { $0 }
}
Here's a version without needing OptionalType (from https://stackoverflow.com/a/36788483/13000)
extension Observable {
/// Returns an `Observable` where the nil values from the original `Observable` are skipped
func unwrap<T>() -> Observable<T> where Element == T? {
self
.filter { $0 != nil }
.map { $0! }
}
}