Flat mapping observable properties in a observable collection in RxSwift - ios

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 } }

Related

How to force firebase functions to execute before continuing swift

I am trying to query data from firebase inside a for loop, my problem is since the queries take time to connect, swift is jumping over the queries and coming back later to do them. This creates the problem where my loop counter is ticking up but the queries are being saved for later, when the queries finally do get executed, the counter variable is all out of wack.
Where the code is being skipped is right after the query, where I am trying to append to an array.
func getSelectedData() {
var exerciseIndex = 0
for i in 0...Master.exercises.count - 1 {
if Master.exercises[i].name == self.exerciseName {
exerciseIndex = i
}
}
let numOfSets = Master.exercises[exerciseIndex].totalSets
// For each date record
for count in 0...self.returnedExercises.count-1 {
// Creates a new dataSet
dataSet.append(dataSetStruct())
dataSet[count].date = returnedExercises[count]
for number in 0...(numOfSets - 1) {
// Retrives the reps
let repsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("reps")
repsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
}
else {
// error
}
}
//Retrives the weights
let weightsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("weights")
weightsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].weightsArray.append(data["Weight\(number + 1)"] as! Float)
self.updateGraph()
}
else {
// error
}
}
}
}
}
I even tried breaking out the query into another function but this doesn't seem to fix the issue.
Any help is appreciated, thanks.
EDIT:
func getSelectedData() {
if returnedExercises.count > 0 {
// Create a dispatch group
let group = DispatchGroup()
print("Getting Data")
// For each date record
for count in 0...self.returnedExercises.count-1 {
// Creates a new dataSet
self.dataSet.append(dataSetStruct())
self.dataSet[count].date = self.returnedExercises[count]
for number in 0...(self.numOfSets - 1) {
print("At record \(count), set \(number)")
// Enter the group
group.enter()
// Start the dispatch
DispatchQueue.global().async {
// Retrives the reps
let repsDbCallHistory = self.db.collection("users").document("\(self.userId)").collection("ExerciseData").document("AllExercises").collection(self.exerciseName).document(self.returnedExercises[count]).collection("Set\(number + 1)").document("reps")
repsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
print("Getting data: \(number)")
group.leave()
}
else {
// error
}
}
}
group.wait()
print("Finished getting data")
}
}
I tried to simplify the function for now and only have one database call in the function to try the dispatch groups. I am not sure why firebase is doing this but the code never executes the group.leave, the program just sits idle. If I am doing something wrong please let me know, thanks.
This is what the print statements are showing:
Getting Data
At record 0, set 0
At record 0, set 1
At record 0, set 2
At record 1, set 0
At record 1, set 1
At record 1, set 2
print("Getting data: (number)") is never being executed for some reason.
I am thinking that maybe firebase calls are ran on a separate thread or something, which would made them pause execution as well, but that's just my theory
EDIT2::
func getOneRepMax(completion: #escaping (_ message: String) -> Void) {
if returnedOneRepMax.count > 0 {
print("Getting Data")
// For each date record
for count in 0...self.returnedOneRepMax.count-1 {
// Creates a new dataSet
oneRPDataSet.append(oneRepMaxStruct())
oneRPDataSet[count].date = returnedOneRepMax[count]
// Retrives the reps
let oneRepMax = db.collection("users").document("\(userId)").collection("UserInputData").document("OneRepMax").collection(exerciseName).document(returnedOneRepMax[count])
oneRepMax.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.oneRPDataSet[count].weight = Float(data["Weight"] as! String)!
print("Getting data: \(count)")
completion("DONE")
self.updateGraph()
}
else {
// error
}
}
}
}
}
I tried using completion handlers for a different function and it is also not working properly.
self.getOneRepMax(completion: { message in
print(message)
})
print("Finished getting data")
The order that the print statements should go:
Getting Data
Getting data: 0
Done
Getting data: 1
Done
Finished getting data
The order that the print statements are coming out right now:
Getting Data
Finished getting data
Getting data: 1
Done
Getting data: 0
Done
I am not even sure how it is possible that the count is backwards since my for loop counts up, what mistake am I making?
I think what you need are Dispatch Groups.
let dispatchGroup1 = DispatchGroup()
let dispatchGroup2 = DispatchGroup()
dispatchGroup1.enter()
firebaseRequest1() { (_, _) in
doThings()
dispatchGroup1.leave()
}
dispatchGroup2.enter()
dispatchGroup1.notify(queue: .main) {
firebaseRequest2() { (_, _ ) in
doThings()
dispatchGroup2.leave()
}
dispatchGroup2.notify(queue: .main) {
completionHandler()
}

Swift Realm - How to COUNT all data from single column?

Here is what my class looks like:
class Card : Object {
#objc dynamic var tags: String = ""
#objc dynamic var set_id: String = ""
}
I want to return number of tags from all Cards with forwarded set_id.
Here is the method:
func totalTags() -> String {
var tagCounter: Int = 0
let realm = try? Realm()
let totalCards = realm!.objects(Card.self).filter("set_id = '\(setId)'") //all Cards with selected set_id, set_id is global var.
for card in 0...totalCards.count {
//every 'card' has tags, but there there can me more tags,
//like : tags="one,twitter,world,Europe"...
//And I want to count all of them for every 'card'
let result = realm.objects(Card.self).filter() //How to filter?
tagCounter += //what? result.count or something?
}
return String(tagCounter)
}
I understand that tags: String contains comma separated elements and you want to find the number of elements.
You can do that by iterating over totalCards. For each card, split the tags into an array and count the number of elements.
for card in totalCards {
tagCounter += card.tags.components(separatedBy: ",").count
}
components(separatedBy:) documentation
I know this was already answered but I just want to share how to do it my own way.
let tagsCount = totalCards.map { $0.tags.components(separatedBy: ",") }.flatMap { $0 }.filter { !$0.isEmpty }.reduce(into: 0, { result, _ in
result += 1
})
Thanks. Happy coding :)

observeNext called more than once after insert element to MutableObservableArray in Bond Swift Framework

I have a MutableObservableArray object which has a binding with observeNext function of Bond framework. At the first opening of the app, I fetch array from user defaults and insert it to this empty array.
My problem is that after I insert element to array, observeNext function called three times not once. What can be the problem?
var list = MutableObservableArray<Task>([])
_ = self.list.observeNext(with: { element in
if element.diff.count != 0 {
if element.diff.deletes.count >= 1 && element.collection.count == 0 {
self.restoreUserDefaults(with: false)
} else {
self.saveListToUserDefaults(list: element.collection)
}
}
})
Insert Function:
if let savedTask = userDef.object(forKey: self.userDefaultsKeyForList) as? Data {
let decoder = JSONDecoder()
if let loadedList = try? decoder.decode([Task].self, from: savedTask) {
self.list.batchUpdate({ (a) in
a.insert(contentsOf: loadedList, at: 0)
})
}
}
EDIT: I think when It first initialize array with [] It also gets into observeNext. Is it normal?

Any magic command to extract an array from array of custom objects?

I have a class like this
class ValueTimestamp {
let value: Double
let timestamp : Double
init(value:Double, timestamp:Double) {
self.value = valuer
self.timestamp = timestamp
}
}
Then I have an array filled with ValueTimestamp objects. Let's call this, myArray.
Now I want to manipulate the array, to extract, for example the elements with values bigger than 10.
Because I am new to swift, I would do this:
// this will create an array with Doubles
let sub = myArray.map($0.value > 10)
var newArray : [ValueTimestamp] = []
for i in 0..< myArray.count {
let newValue = ValueTimestamp.init(value:sub[i], timestamp:myArray[i])
newArray.append(newValue)
}
and now I have newArray that contains the elements from myArray with values bigger than 10.
Is there any magic command using .map, .flatmap or whatever that can do this?
What you looking for is filter method:
public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]
It takes as parameter closure, which take 1 element and return true if element should be added in resulting array or false if it should be filtered out.
Your code:
let biggerThem10 = myArray.filter { $0.value > 10 }

How do I calculate the items in a class' property? Swift language

A method should calculate the price of all the items in a class’s property. Unfortunately, my knowledge when it comes to programming is ultra limited. Any help would be very much appreciated.
Thanks in advance.
In Swift3
using reduce
func priceBeforeDiscounts() -> Int {
return items.reduce(0) { (result, item) in
return result + item.priceInPence
}
}
using for in loop
func priceBeforeDiscounts() -> Int {
var sum: Int = 0
for item in items {
sum += item.priceInPence
}
return sum
}
Here you have some useful code (prepared in playground) for you:
// This is your item class
class Item {
var priceInPence: Int = 0
init(price: Int) {
priceInPence = price
}
}
// This is your array of items
var items = [Item]()
// Here I create items with given price
for _ in 0...3 {
items.append(Item(price: 2))
}
// This is the most interesting part for you, here you sum up prices from items in array
let sum = items.reduce(0, { $0 + $1.priceInPence })
print(sum)
I created this code in playground, so you can copy/past this code to your playground and play with it. Having my example you should be able to update your source code.

Resources