Get specific .childrenCount from Firebase - swift

In my new app (Project Control, iOS App Store ;)) I want users to take part of development decisions. For this I have added a path in my Firebase database called "claps". I would like to enter the number of the following in my TableView for the different concepts. I have tried the following
self.posts.append(Post(title: post_title, des: post_description, info: "\(post_date) - \(post_user) - \(post_claps) đź‘Ź", claps: Int(post_claps)))
for var item in self.posts {
g.ref.child("concepts").child(item.title).queryOrdered(byChild: "claps").observe(.childAdded) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
}
DispatchQueue.main.async() {
self.tableView.reloadData()
}
However, it does not yet represent the right one, but is one before it. I don't know how to make the reference more specific to really get only what's under claps.
This ist my Database:
Currently my output is 5 but it should be 4. You see its observing one "layer" to early. Help will be appreciated. Improvements too :)
UPDATE:
Through testing I could reveal that the problem is in the reference. The Int five is coming from the 5 Childs of "top-layer" "Journal". My problem is that I cant get any deeper in the structure because I don't have a specific String for .child()

Since you're observing the .childAdded event, your closure gets called for each matching child node. If you want to count the number of matching child nodes, you'll want to observe the .value event, which ensures your closure gets called for all matching nodes at once.
Something like:
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
Note that I also removed the orderBy clause, since that has no useful meaning if all you use is the count.

create an Array and allow the firebase to populate it. or do something like
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
observing value makes sure your closure gets its matching nodes.

There's a couple of great solutions but the issue in reading a node by .value is it reads in everything in that node.
While that would be fine for nodes that have a limited amount of data, it would overwhelm the device when the node contains a lot of data.
So another option is to leverage that Firebase executes all .childAdded events before .value events. That way, we can use a .value as a trigger that all nodes have been read.
Here's a function that uses .childAdded to iterate and count all of the users in the users node. Also, there's a .value observer that reads in just the last node, removes the .childAdded observer and passes the count back to the calling function via a completion handler. Remember that even though we are attaching both observers, the .childAdded events will all fire before the .value event.
func countUsers( completion: #escaping(Int) -> Void) {
var count = 0
let usersRef = self.ref.child("users")
usersRef.observe(.childAdded, with: { snapshot in
count+=1
})
let query = usersRef.queryOrderedByKey().queryLimited(toLast: 1)
query.observeSingleEvent(of: .value, with: { snapshot in
usersRef.removeAllObservers()
completion(count)
})
}
to call the function, here's the code
func getUserCount() {
self.countUsers(completion: { userCount in
print("number of users: \(userCount)")
})
}

Related

Combine prefetching and caching results

I'm looking for a way to prefetch data and cache it without any subscriptions made towards that subscriber or when first subscription is made.
For example :
products = getProducts.shareReply(scope : forever)
And when new subscribers subscribes it should receive the last data. If there is no subscribers it's shouldn't restart this publisher on next subscription instead it should return last cached data.
I'm already using shareReply operator with multicast, custom ReplaySubject and autoconnect but if there is no subscriptions towards the publisher next time something subscribes it will restart the publisher and new data will be fetched. And I don't want that.
This is code for shareReply operator :
extension Publisher {
func shareReplay(_ bufferSize: Int = 1) -> AnyPublisher<Output, Failure> {
return multicast(subject: ShareReplySubject(bufferSize))
.autoconnect()
.eraseToAnyPublisher()
}
}
I'm aware of it but I would like to turn AnyPublisher to something like Behaviour(CurrentValue)Subject I mean I want to turn anyPublisher to "hot publisher" and so it emits data regardless of if there is a subscriber or not.
You can do this by applying the multicast operator and calling connect on the result:
let (sharedProducts, sharedProductsTicket): (AnyPublisher<[Product], Never>, Cancellable) = {
let pub = getProducts()
.multicast(subject: CurrentValueSubject([]))
return (pub.eraseToAnyPublisher(), pub.connect())
}()
Now sharedProducts has a live subscription to getProducts() and stores the latest value published by getProducts(). Anyone who subscribes to sharedProducts immediately receives the latest value, and receives new values as they arrive.
The pub.connect() call returns a Cancellable that, when destroyed, cancels the inner subscription connecting getProducts() to the CurrentValueSubject. So you have to store that ticket somewhere. In my example, I store it in sharedProductsTicket.

Mutating of RxSwift Driver value

In my application I have an array of notifications. Notification can be read and unread.
When user clicks on the unread notification I need to change the model and reload data in my table view.
In my ViewModel I have output stream:
let notifications: Driver<[Notification]>
And aslo I have an input stream with notification click:
let touchSingleNotificationIntent = PublishSubject<Notification>()
When I do something like this I get the error that it's let constant and I cannot mutate it.
touchSingleNotificationIntent
.filter { !$0.isRead }
.do(onNext: { notification in
notification.isRead = true // I need to change state of the model immediately after user iteration
})
.map { $0.notificationID }
.flatMap(markNotificationAsRead) // http request which doesn't reply with current notification model status
.subscribe()
.disposed(by: bag)
Do you have any ideas how to make it mutable? Thanks.
Streams aren't Mutable at all (this is the same for Observable, Driver, and any other traits). They are "Read only", you read values off the stream over time.
In general, the conception Observables has a "value" is a bit wrong since Observables represent a value over time, and not just a single value.
What you would want to do is "take into account" your PublishSubject when building out your driver.
Something like this would work:
notifications = Observable
.combineLatest(touchedNotification, readNotification, otherEvent) { ($0, $1, $2) }
.map { ... map the three values into whatever makes sense for you }
.asDriver(onErrorJustReturn: ... fallback value ... }
Again, the most important fact to remember - You do not actually mutate streams, you only combine them, transform them, etc, to create a new stream that suits your needs.
Hope this helps you!
Parameters of onNext are let by default. You can define a new one with var, i.e. 'var newNotification = notification' and then return it after modifying.

Firebase query slow code execution: append to table

I was hoping I could get some help optimising my code. I´m new to development so please be kind.
Currently it works, but it uses quite some time (10-15 sec) to load the first table view I need in my app.
First I thought that I had not activated "persistence" properly, but I am starting to suspect that it is the way I am loading data that is suboptimal.
The "large" (12k + items) data set I use dont change that frequently, so the ideal solution would be to load that once, then listen for changes. I thought that was what I am doing, but if so I dont understand why it is so slow? So I now suspect that it is the way that I append the data every time, instead of just "reading/loading" from "somewhere local" and then listen for changes from the sever?
Any help is appreciated
//read From Firebase adjusted to whiskies
func startObservingDB () {
dbRef.queryOrdered(byChild: "brand_name").observe(.value, with: { (snapshot:FIRDataSnapshot) in
var newWhisky = [WhiskyItem]()
//forloop to iterate through the snapshot
for whiskyItem in snapshot.children {
let whiskyObject = WhiskyItem(snapshot: whiskyItem as! FIRDataSnapshot)
newWhisky.append(whiskyObject)
}
//update
self.whiskies = newWhisky
print("WhiskyItem")
self.tableView.reloadData()
}) { (error: Error) in
print(error.localizedDescription)
}
}
Firebase structure: /Results/Index/name: xxx, "other thing1": xxxx,..., "other thing32": xxxx
I'm not sure, that it is a good idea to store all 12 000 items on your phone.
May be it will be good solution for you:
You can use this lib for:
(example)
1) load data for 100 rows
2) scroll to the end
3) do another load of 100 rows.
Hope it helps

Siesta handling multiple requests

I have a loop where I POST requests to the server:
for (traineeId, points) in traineePointsDict {
// create a new point
let parameters: NSDictionary = [
"traineeId": "\(traineeId)",
"numPoints": points,
"exerciseId": "\(exerciseId)"
]
DataManager.sharedInstance.api.points.request(.POST, json: parameters).success { data in
if data.json["success"].int == 1 {
self.pointCreated()
} else {
self.pointFailToCreate()
}
}.failure { error in
self.pointFailToCreate()
}
}
The problem is that for some reason the last request fails and I am guessing that this is due to posting too many requests to the server at the same time.
Is there a way to chain these requests so they wait for the one before to complete before executing the next?
I have been looking at PromiseKit, but I don't really know how to implement this and I am looking for a quick solution.
Siesta does not control how requests are queued or how many requests run concurrently. You have two choices:
control it on the app side, or
control it in the network layer.
I’d investigate option 2 first. It gives you less fine-grained control, but it give you more robust options on the cheap and is less prone to mistakes. If you are using URLSession as your networking layer (which is Siesta’s default), then investigate whether the HTTPMaximumConnectionsPerHost property of URLSessionConfiguration does what you need. (Here are some examples of passing custom configuration to Siesta.)
If that doesn’t work for you, a simple version of #1 is to use a completion handler to chain the requests:
func chainRequests(_ queue: [ThingsToRequest])
guard let thing = queue.first else { return }
params = makeParamsFor(thing)
resource.request(.POST, json: params)
.onSuccess {
...
}.onFailure {
...
}.onCompletion { _ in
chainRequests(queue[1 ..< queue.count])
}
}
Note that you can attach multiple overlapping handlers to the same request, and they’re called in the order you attached them. Note also that Siesta guarantees that the completion block is always called, no matter the outcome. This means that each request will result in calls to either closures 1 & 3 or closures 2 & 3. That’s why this approach works.

RXJS : Idiomatic way to create an observable stream from a paged interface

I have paged interface. Given a starting point a request will produce a list of results and a continuation indicator.
I've created an observable that is built by constructing and flat mapping an observable that reads the page. The result of this observable contains both the data for the page and a value to continue with. I pluck the data and flat map it to the subscriber. Producing a stream of values.
To handle the paging I've created a subject for the next page values. It's seeded with an initial value then each time I receive a response with a valid next page I push to the pages subject and trigger another read until such time as there is no more to read.
Is there a more idiomatic way of doing this?
function records(start = 'LATEST', limit = 1000) {
let pages = new rx.Subject();
this.connect(start)
.subscribe(page => pages.onNext(page));
let records = pages
.flatMap(page => {
return this.read(page, limit)
.doOnNext(result => {
let next = result.next;
if (next === undefined) {
pages.onCompleted();
} else {
pages.onNext(next);
}
});
})
.pluck('data')
.flatMap(data => data);
return records;
}
That's a reasonable way to do it. It has a couple of potential flaws in it (that may or may not impact you depending upon your use case):
You provide no way to observe any errors that occur in this.connect(start)
Your observable is effectively hot. If the caller does not immediately subscribe to the observable (perhaps they store it and subscribe later), then they'll miss the completion of this.connect(start) and the observable will appear to never produce anything.
You provide no way to unsubscribe from the initial connect call if the caller changes its mind and unsubscribes early. Not a real big deal, but usually when one constructs an observable, one should try to chain the disposables together so it call cleans up properly if the caller unsubscribes.
Here's a modified version:
It passes errors from this.connect to the observer.
It uses Observable.create to create a cold observable that only starts is business when the caller actually subscribes so there is no chance of missing the initial page value and stalling the stream.
It combines the this.connect subscription disposable with the overall subscription disposable
Code:
function records(start = 'LATEST', limit = 1000) {
return Rx.Observable.create(observer => {
let pages = new Rx.Subject();
let connectSub = new Rx.SingleAssignmentDisposable();
let resultsSub = new Rx.SingleAssignmentDisposable();
let sub = new Rx.CompositeDisposable(connectSub, resultsSub);
// Make sure we subscribe to pages before we issue this.connect()
// just in case this.connect() finishes synchronously (possible if it caches values or something?)
let results = pages
.flatMap(page => this.read(page, limit))
.doOnNext(r => this.next !== undefined ? pages.onNext(this.next) : pages.onCompleted())
.flatMap(r => r.data);
resultsSub.setDisposable(results.subscribe(observer));
// now query the first page
connectSub.setDisposable(this.connect(start)
.subscribe(p => pages.onNext(p), e => observer.onError(e)));
return sub;
});
}
Note: I've not used the ES6 syntax before, so hopefully I didn't mess anything up here.