How to ignore behaviorRelay element removal on RxSwift? - ios

I'm observing a BehaviorRelay and I want to subscribe only when the number of items increase. I tried distinct/dinstinctUntilChanged but it does not suit my needs because it will skip too much or too few times.
behaviorRelay
.compactMap { $0?.items }
.subscribe(onNext: { elements in
print("items has one more element.")
}).disposed(by: bag)
var behaviorRelay = BehaviorRelay<[Car]?>(value: [])
class Car {
var items: [Any] // whatever... just an example.
}

First of all, use map to map from array to number (of elements):
.map { $0?.count ?? 0 } // return 0 if array is nil
Than use scan, to retrieve both current and previous element, like this:
.scan((0, 0)) { previousPairOfValues, newValue in
return (previousPairOfValues.1, newValue) // create new pair from newValue and second item of previous pair
}
Then use filter, to only pass increasing values:
.filter { $0.1 > $0.0 } // newer value greater than older value
Than map it back to the latest value:
.map { $0.1 }
Putting all together:
behaviorRelay
.compactMap { $0?.items }
.map { $0?.count ?? 0 } // return 0 if array is nil
.scan((0, 0)) { previousPairOfValues, newValue in
return (previousPairOfValues.1, newValue) // create new pair from newValue and second item of previous pair
}
.filter { $0.1 > $0.0 } // newer value greater than older value
.map { $0.1 }
.subscribe(onNext: { elementCount in
print("items has one more element.")
print("there are \(elementCount) items now")
}).disposed(by: bag)

Related

RxSwift - block/stop/ignore event if condition met

After filtering an Observable list, I might have an empty list. I'm only interested in events that contain a populated list. Is there any way to stop empty events propagating to onNext?
let source: BehaviorRelay<[Int]> = .init(value: [])
source
.map { nums -> [Int] in
return nums.filter { $0 < 10 }
}
/// What can go here to stop/block/ignore an empty list
.subscribe(onNext: { nums in
print("OKPDX \(nums)")
})
source.accept([1, 9, 13])
// prints "[1, 9]" (all good)
source.accept([20, 22, 101])
// prints "[]" (not desirable, I'd rather not know)
What about using .filter? You could do this:
.map { nums -> [Int] in
return nums.filter { $0 < 10 }
}
.filter { !$0.isEmpty }
Or you could also validate that whenever you get an event like this:
.subscribe(onNext: { nums in
guard !nums.isEmpty else { return }
print("OKPDX \(nums)")
})

RxSwift Use withLatestFrom operator with multiple sources

I have 3 observables namely source, source1 and source2. What I want is whenever source emits distinct event get the value of source1 and source2. This is the code I've come up with, obviously it won't compile since withLatestFrom expects only one observable.
source.distinctUntilChanged()
.withLatestFrom(source1, source2) { ($0, $1.0, $1.1) }
.subscribe(onNext: { (A, B, C) in
print("OnNext called")
})
.disposed(by: bag)
You almost have it. How about just combining source1 and source2?
source.distinctUntilChanged()
.withLatestFrom(Observable.combineLatest(source1, source2)) { ($0, $1.0, $1.1) }
.subscribe(onNext: { (A, B, C) in
print("OnNext called")
})
.disposed(by: bag)
You can use something like this:
valuesSource
.withUnretained(self) { ($0, $1.0, $1.1, $1.2, $1.3) }
.withLatestFrom(transactionSource) { ($0, $1) }
.withLatestFrom(userSource) { ($0.0, $0.1, $1) }
.map { values, transaction, user -> [SectionType] in
Hope this helps :)
You can do something like this
let source = PublishSubject<Int>()
let source1 = PublishSubject<Int>()
let source2 = PublishSubject<Int>()
Observable
.combineLatest([source,source1,source2])
.distinctUntilChanged { (oldArray, newArray) -> Bool in
return oldArray.first == newArray.first
}
.subscribe(onNext: { (values) in
debugPrint("source1 value is \(values[1]) and source2 value is \(values[2])")
})
.disposed(by: self.disposeBag)
source1.onNext(100)
source2.onNext(200)
source.onNext(3)
source.onNext(4)
source.onNext(4)
O/P will look like
Catch:
I am using combineLatest that means this will work only after all the observables emitted values at least once. You can always use startWith operator to ensure all your observables emits value at least once.

Flat mapping observable properties in a observable collection in RxSwift

Using RxSwift, say I have a class A that contains an observable of integer
class A: {
let count: Observable<Int>
}
and a observable collection of objects of A
let data: Observable<[A]>
I want to define a sum: Observable<Int> that will be the sum of all the count on all the objects in data. Whenever the data observable collection changes or any of the count property changes the sum should also change.
How to achieve this? I tried some flapMap and map combinations but only got to a solution when sum gets updated only when data gets updated, ignoring the count changes.
let sum = data.flatMap { Observable.from($0).flatMap { $0.count }.reduce(0, accumulator: +) }
'.reduce()' emits only on completion, so it has to be inside of the outer '.flatMap()'
Update 1:
let sumSubject: BehaviorSubject<Observable<Int>> = BehaviorSubject.create(Observable.empty())
let sum = sumSubject.switchLatest()
// every time it has to be a new 'data' observable!
sumSubject.onNext(data.flatMap { Observable.from($0).flatMap { $0.count }.reduce(0, accumulator: +) })
Update 2:
let counts: Observable<[Int]> = data.flatMap { Observable.combineLatest($0.map { $0.count }) }
let sum: Observable<Int> = counts.map { $0.reduce(0) { $0 + $1 } }

Rx Observable that gets value from other Observable

I am new to RxSwift and MVVM.
my viewModel has a method named rx_fetchItems(for:) that does the heavy lifting of fetching relevant content from backend, and returns Observable<[Item]>.
My goal is to supply an observable property of the viewModel named collectionItems, with the last emitted element returned from rx_fetchItems(for:), to supply my collectionView with data.
Daniel T has provided this solution that I could potentially use:
protocol ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]>
}
struct ViewModel {
let collectionItems: Observable<[Item]>
let error: Observable<Error>
init(controlValue: Observable<Int>, api: ServerAPI) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! } // or use a `filterNil` operator if you already have one implemented.
.flatMap { api.rx_fetchItems(for: $0)
.materialize()
}
.filter { $0.isCompleted == false }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
The only problem here is that my current ServerAPI aka FirebaseAPI, has no such protocol method, because it is designed with a single method that fires all requests like this:
class FirebaseAPI {
private let session: URLSession
init() {
self.session = URLSession.shared
}
/// Responsible for Making actual API requests & Handling response
/// Returns an observable object that conforms to JSONable protocol.
/// Entities that confrom to JSONable just means they can be initialized with json.
func rx_fireRequest<Entity: JSONable>(_ endpoint: FirebaseEndpoint, ofType _: Entity.Type ) -> Observable<[Entity]> {
return Observable.create { [weak self] observer in
self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in
/// Parse response from request.
let parsedResponse = Parser(data: data, response: response, error: error)
.parse()
switch parsedResponse {
case .error(let error):
observer.onError(error)
return
case .success(let data):
var entities = [Entity]()
switch endpoint.method {
/// Flatten JSON strucuture to retrieve a list of entities.
/// Denoted by 'GETALL' method.
case .GETALL:
/// Key (underscored) is unique identifier for each entity, which is not needed here.
/// value is k/v pairs of entity attributes.
for (_, value) in data {
if let value = value as? [String: AnyObject], let entity = Entity(json: value) {
entities.append(entity)
}
}
// Need to force downcast for generic type inference.
observer.onNext(entities as! [Entity])
observer.onCompleted()
/// All other methods return JSON that can be used to initialize JSONable entities
default:
if let entity = Entity(json: data) {
observer.onNext([entity] as! [Entity])
observer.onCompleted()
} else {
observer.onError(NetworkError.initializationFailure)
}
}
}
}).resume()
return Disposables.create()
}
}
}
The most important thing about the rx_fireRequest method is that it takes in a FirebaseEndpoint.
/// Conforms to Endpoint protocol in extension, so one of these enum members will be the input for FirebaseAPI's `fireRequest` method.
enum FirebaseEndpoint {
case saveUser(data: [String: AnyObject])
case fetchUser(id: String)
case removeUser(id: String)
case saveItem(data: [String: AnyObject])
case fetchItem(id: String)
case fetchItems
case removeItem(id: String)
case saveMessage(data: [String: AnyObject])
case fetchMessages(chatroomId: String)
case removeMessage(id: String)
}
In order to use Daniel T's solution, Id have to convert each enum case from FirebaseEndpoint into methods inside FirebaseAPI. And within each method, call rx_fireRequest... If I'm correct.
Id be eager to make this change if it makes for a better Server API design. So the simple question is, Will this refactor improve my overall API design and how it interacts with ViewModels. And I realize this is now evolving into a code review.
ALSO... Here is implementation of that protocol method, and its helper:
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = client.rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { [weak self] (itemList) -> Observable<[Item]> in
return self!.rx_localItems(items: itemList)
}
return localItems
// TODO: Handle other cases like RecentlyAdded, Trending, etc..
}
}
// Helper method to filter items for only local items nearby user.
private func rx_localItems(items: [Item]) -> Observable<[Item]> {
return Observable.create { observable in
observable.onNext(items.filter { $0.location == "LA" })
observable.onCompleted()
return Disposables.create()
}
}
If my approach to MVVM or RxSwift or API design is wrong PLEASE do critique.
I know it is tough to start understanding RxSwift
I like to use Subjects or Variables as inputs for the ViewModel and Observables or Drivers as outputs for the ViewModel
This way you can bind the actions that happen on the ViewController to the ViewModel, handle the logic there, and update the outputs
Here is an example by refactoring your code
View Model
// Inputs
let didSelectItemCategory: PublishSubject<ItemCategory> = .init()
// Outputs
let items: Observable<[Item]>
init() {
let client = FirebaseAPI()
let fetchedItems = client.rx_fireRequest(.fetchItems, ofType: Item.self)
self.items = didSelectItemCategory
.withLatestFrom(fetchedItems, resultSelector: { itemCategory, fetchedItems in
switch itemCategory {
case .Local:
return fetchedItems.filter { $0.location == "Los Angeles" }
default: return []
}
})
}
ViewController
segmentedControl.rx.value
.map(ItemCategory.init(rawValue:))
.startWith(.Local)
.bind(to: viewModel.didSelectItemCategory)
.disposed(by: disposeBag)
viewModel.items
.subscribe(onNext: { items in
// Do something
})
.disposed(by: disposeBag)
I think the problem you are having is that you are only going half-way with the observable paradigm and that's throwing you off. Try taking it all the way and see if that helps. For example:
protocol ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]>
}
struct ViewModel {
let collectionItems: Observable<[Item]>
let error: Observable<Error>
init(controlValue: Observable<Int>, api: ServerAPI) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! } // or use a `filterNil` operator if you already have one implemented.
.flatMap { api.rx_fetchItems(for: $0)
.materialize()
}
.filter { $0.isCompleted == false }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
EDIT to handle problem mentioned in comment. You now need to pass in the object that has the rx_fetchItems(for:) method. You should have more than one such object: one that points to the server and one that doesn't point to any server, but instead returns canned data so you can test for any possible response, including errors. (The view model should not talk to the server directly, but should do so through an intermediary...
The secret sauce in the above is the materialize operator that wraps error events into a normal event that contains an error object. That way you stop a network error from shutting down the whole system.
In response to the changes in your question... You can simply make the FirebaseAPI conform to ServerAPI:
extension FirebaseAPI: ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = self.rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { [weak self] (itemList) -> Observable<[Item]> in
return self!.rx_localItems(items: itemList)
}
return localItems
// TODO: Handle other cases like RecentlyAdded, Trending, etc..
}
}
// Helper method to filter items for only local items nearby user.
private func rx_localItems(items: [Item]) -> Observable<[Item]> {
return Observable.create { observable in
observable.onNext(items.filter { $0.location == "LA" })
observable.onCompleted()
return Disposables.create()
}
}
}
You should probably change the name of ServerAPI at this point to something like FetchItemsAPI.
You run into a tricky situation here because your observable can throw an error and once it does throw an error the observable sequence errors out and no more events can be emitted. So to handle subsequent network requests, you must reassign taking the approach you're currently taking. However, this is generally not good for driving UI elements such as a collection view because you would have to bind to the reassigned observable every time. When driving UI elements, you should lean towards types that are guaranteed to not error out (i.e. Variable and Driver). You could make your Observable<[Item]> to be let items = Variable<[Item]>([]) and then you could just set the value on that variable to be the array of items that came in from the new network request. You can safely bind this variable to your collection view using RxDataSources or something like that. Then you could make a separate variable for the error message, let's say let errorMessage = Variable<String?>(nil), for the error message that comes from the network request and then you could bind the errorMessage string to a label or something like that to display your error message.

Check if optional array is empty

In Objective-C, when I have an array
NSArray *array;
and I want to check if it is not empty, I always do:
if (array.count > 0) {
NSLog(#"There are objects!");
} else {
NSLog(#"There are no objects...");
}
That way, there is no need to check if array == nil since this situation will lead the code to fall into the else case, as well as a non-nil but empty array would do.
However, in Swift, I have stumbled across the situation in which I have an optional array:
var array: [Int]?
and I am not being able to figure out which condition to use. I have some options, like:
Option A: Check both non-nil and empty cases in the same condition:
if array != nil && array!.count > 0 {
println("There are objects")
} else {
println("No objects")
}
Option B: Unbind the array using let:
if let unbindArray = array {
if (unbindArray.count > 0) {
println("There are objects!")
} else {
println("There are no objects...")
}
} else {
println("There are no objects...")
}
Option C: Using the coalescing operator that Swift provides:
if (array?.count ?? 0) > 0 {
println("There are objects")
} else {
println("No objects")
}
I do not like the option B very much, because I am repeating code in two conditions. But I am not really sure about whether options A and C are correct or I should use any other way of doing this.
I know that the use of an optional array could be avoided depending on the situation, but in some case it could be necessary to ask if it is empty. So I would like to know what is the way to do it the simplest way.
EDIT:
As #vacawama pointed out, this simple way of checking it works:
if array?.count > 0 {
println("There are objects")
} else {
println("No objects")
}
However, I was trying the case in which I want to do something special only when it is nil or empty, and then continue regardless whether the array has elements or not. So I tried:
if array?.count == 0 {
println("There are no objects")
}
// Do something regardless whether the array has elements or not.
And also
if array?.isEmpty == true {
println("There are no objects")
}
// Do something regardless whether the array has elements or not.
But, when array is nil, it does not fall into the if body. And this is because, in that case, array?.count == nil and array?.isEmpty == nil, so the expressions array?.count == 0 and array?.isEmpty == true both evaluate to false.
So I am trying to figure out if there is any way of achieve this with just one condition as well.
Updated answer for Swift 3 and above:
Swift 3 has removed the ability to compare optionals with > and <, so some parts of the previous answer are no longer valid.
It is still possible to compare optionals with ==, so the most straightforward way to check if an optional array contains values is:
if array?.isEmpty == false {
print("There are objects!")
}
Other ways it can be done:
if array?.count ?? 0 > 0 {
print("There are objects!")
}
if !(array?.isEmpty ?? true) {
print("There are objects!")
}
if array != nil && !array!.isEmpty {
print("There are objects!")
}
if array != nil && array!.count > 0 {
print("There are objects!")
}
if !(array ?? []).isEmpty {
print("There are objects!")
}
if (array ?? []).count > 0 {
print("There are objects!")
}
if let array = array, array.count > 0 {
print("There are objects!")
}
if let array = array, !array.isEmpty {
print("There are objects!")
}
If you want to do something when the array is nil or is empty, you have at least 6 choices:
Option A:
if !(array?.isEmpty == false) {
print("There are no objects")
}
Option B:
if array == nil || array!.count == 0 {
print("There are no objects")
}
Option C:
if array == nil || array!.isEmpty {
print("There are no objects")
}
Option D:
if (array ?? []).isEmpty {
print("There are no objects")
}
Option E:
if array?.isEmpty ?? true {
print("There are no objects")
}
Option F:
if (array?.count ?? 0) == 0 {
print("There are no objects")
}
Option C exactly captures how you described it in English: "I want to do something special only when it is nil or empty." I would recommend that you use this since it is easy to understand. There is nothing wrong with this, especially since it will "short circuit" and skip the check for empty if the variable is nil.
Previous answer for Swift 2.x:
You can simply do:
if array?.count > 0 {
print("There are objects")
} else {
print("No objects")
}
As #Martin points out in the comments, it uses func ><T : _Comparable>(lhs: T?, rhs: T?) -> Bool which means that the compiler wraps 0 as an Int? so that the comparison can be made with the left hand side which is an Int? because of the optional chaining call.
In a similar way, you could do:
if array?.isEmpty == false {
print("There are objects")
} else {
print("No objects")
}
Note: You have to explicitly compare with false here for this to work.
If you want to do something when the array is nil or is empty, you have at least 7 choices:
Option A:
if !(array?.count > 0) {
print("There are no objects")
}
Option B:
if !(array?.isEmpty == false) {
print("There are no objects")
}
Option C:
if array == nil || array!.count == 0 {
print("There are no objects")
}
Option D:
if array == nil || array!.isEmpty {
print("There are no objects")
}
Option E:
if (array ?? []).isEmpty {
print("There are no objects")
}
Option F:
if array?.isEmpty ?? true {
print("There are no objects")
}
Option G:
if (array?.count ?? 0) == 0 {
print("There are no objects")
}
Option D exactly captures how you described it in English: "I want to do something special only when it is nil or empty." I would recommend that you use this since it is easy to understand. There is nothing wrong with this, especially since it will "short circuit" and skip the check for empty if the variable is nil.
Extension Property on the Collection Protocol
*Written in Swift 3
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case .some(let collection):
return collection.isEmpty
case .none:
return true
}
}
}
Example Use:
if array.isNilOrEmpty {
print("The array is nil or empty")
}
Other Options
Other than the extension above, I find the following option most clear without force unwrapping optionals. I read this as unwrapping the optional array and if nil, substituting an empty array of the same type. Then, taking the (non-optional) result of that and if it isEmpty execute the conditional code.
Recommended
if (array ?? []).isEmpty {
print("The array is nil or empty")
}
Though the following reads clearly, I suggest a habit of avoiding force unwrapping optionals whenever possible. Though you are guaranteed that array will never be nil when array!.isEmpty is executed in this specific case, it would be easy to edit it later and inadvertently introduce a crash. When you become comfortable force unwrapping optionals, you increase the chance that someone will make a change in the future that compiles but crashes at runtime.
Not Recommended!
if array == nil || array!.isEmpty {
print("The array is nil or empty")
}
I find options that include array? (optional chaining) confusing such as:
Confusing?
if !(array?.isEmpty == false) {
print("The array is nil or empty")
}
if array?.isEmpty ?? true {
print("There are no objects")
}
Swift extension:
extension Optional where Wrapped: Collection {
var nilIfEmpty: Optional {
switch self {
case .some(let collection):
return collection.isEmpty ? nil : collection
default:
return nil
}
}
var isNilOrEmpty: Bool {
switch self {
case .some(let collection):
return collection.isEmpty
case .none:
return true
}
}
Usage:
guard let array = myObject?.array.nilIfEmpty else { return }
or:
if myObject.array.isNilOrEmpty {
// Do stuff here
}
Option D: If the array doesn't need to be optional, because you only really care if it's empty or not, initialise it as an empty array instead of an optional:
var array = [Int]()
Now it will always exist, and you can simply check for isEmpty.
Conditional unwrapping:
if let anArray = array {
if !anArray.isEmpty {
//do something
}
}
EDIT: Possible since Swift 1.2:
if let myArray = array where !myArray.isEmpty {
// do something with non empty 'myArray'
}
EDIT: Possible since Swift 2.0:
guard let myArray = array where !myArray.isEmpty else {
return
}
// do something with non empty 'myArray'
The elegant built-in solution is Optional's map method. This method is often forgotten, but it does exactly what you need here; it allows you to send a message to the thing wrapped inside an Optional, safely. We end up in this case with a kind of threeway switch: we can say isEmpty to the Optional array, and get true, false, or nil (in case the array is itself nil).
var array : [Int]?
array.map {$0.isEmpty} // nil (because `array` is nil)
array = []
array.map {$0.isEmpty} // true (wrapped in an Optional)
array?.append(1)
array.map {$0.isEmpty} // false (wrapped in an Optional)
It's better to use if to check for empty array. Why bcoz, for example if we try to find greatest integer in a list array, obviously we will compare list[0] with every integer in a list. at that time it gives us Index out of range exception.... you can try this with both IF & Guard
func findGreatestInList(list: [Int]?)-> Int? {
if list!.count == 0 {
print("List is empty")
return nil
}
/*guard list!.isEmpty else {
print("List is empty")
return nil
}*/
var greatestValue = list![0]
for number in 0...list!.count-1 {
if list![number] > greatestValue {
greatestValue = list![number]
Instead of using if and else it is better way just to use guard to check for empty array without creating new variables for the same array.
guard !array.isEmpty else {
return
}
// do something with non empty ‘array’

Resources