I am working on a similar feature to 'liking/unliking a post'.
I have an MVVM architecture as;
struct MyStructModel {
var isLiked: Bool? = false
}
class MyStructView {
var isLiked: Bool
init(myStructModel: MyStructModel) {
self.isLiked = myStructModel.isLiked ?? false
}
}
I successfully get the value of whether the post is liked or not here;
func isPostLiked(documentID: String, completion: #escaping (Bool) -> Void) {
guard let authID = auth.id else { return }
let query = reference(to: .users).document(authID).collection("liked").document(documentID)
query.getDocument { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let data = snapshot?.data() else { return }
if let value = data["isLiked"] as? Bool {
completion(value)
} else {
completion(false)
}
}
}
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let snapshotDocuments = snapshot?.documents else { return }
for document in snapshotDocuments {
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
// isLiked is nil here...
self.isPostLiked(documentID: post.documentID!) { (isLiked) in
post.isLiked = isLiked
print("MODEL SAYS: \(post.isLiked!)")
// isLiked is correct value here...
}
posts.append(post)
}
completion(posts)
}
}
}
However, when it gets to my cell the value is still nil.
Adding Cell Code:
var post: MyStructView? {
didSet {
guard let post = post else { return }
print(post.isLiked!)
}
}
Your isLiked property is likely nil in your cells because the retrieveReviews function doesn't wait for the isPostLiked function to complete before completing itself.
You could easily solve this issue by using DispatchGroups. This would allow you to make sure all of your Posts have their isLiked value properly set before being inserted in the array, and then simply use the DispatchGroup's notify block to return all the loaded posts via the completion handler:
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { [weak self] (snapshot, error) in
guard let self = self else { return }
if error != nil {
return
}
guard let documents = snapshot?.documents else { return }
let dispatchGroup = DispatchGroup()
for document in documents {
dispatchGroup.enter()
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
self.isPostLiked(documentID: post.documentID!) { isLiked in
post.isLiked = isLiked
posts.append(post)
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
completion(posts)
}
}
}
I am trying to build methods with completion blocks for nested requests. The issue is that a completion block catches to early for parent requests (meaning that the child requests haven't actually completed yet). So far I haven't found a way for a child request to communicate back to the parent request other than what I've done in my example below (which is to count the amount of child requests have completed and compare it against the expected amount of requests).
The example below is working against a Firestore database. Imagine a user has multiple card games (decks) with each multiple cards. I'm grateful for any help how to build better completion blocks for cases like these:
func fetchCardsCount(uid: String, completion: #escaping (Int) -> ()) {
let db = Firestore.firestore()
var decksCount = Int()
var cardsCount = Int()
db.collection("users").document(uid).collection("decks").getDocuments { (deckSnapshot, err) in
if let err = err {
print("Error fetching decks for user: ", err)
} else {
guard let deckSnapshot = deckSnapshot else { return }
deckSnapshot.documents.forEach({ (deck) in
let dictionary = deck.data() as [String: Any]
let deck = FSDeck(dictionary: dictionary)
db.collection("users").document(uid).collection("decks").document(deck.deckId).collection("cards").getDocuments(completion: { (cardSnapshot, err) in
if let err = err {
print("Error fetching cards for deck: ", err)
} else {
guard let cardSnapshot = cardSnapshot else { return }
decksCount += 1
cardsCount += cardSnapshot.count
if decksCount == deckSnapshot.count {
completion(cardsCount)
}
}
})
})
}
}
}
Here is the solution, using DispatchGroup, found with #meggar's help in the comments:
func fetchCardsCount(uid: String, completion: #escaping (Int) -> ()) {
let db = Firestore.firestore()
var cardsCount = Int()
let group = DispatchGroup()
group.enter()
db.collection("users").document(uid).collection("decks").getDocuments { (deckSnapshot, err) in
if let err = err {
print("Error fetching decks for user: ", err)
} else {
guard let deckSnapshot = deckSnapshot else { return }
deckSnapshot.documents.forEach({ (deck) in
let dictionary = deck.data() as [String: Any]
let deck = FSDeck(dictionary: dictionary)
group.enter()
db.collection("users").document(uid).collection("decks").document(deck.deckId).collection("cards").getDocuments(completion: { (cardSnapshot, err) in
if let err = err {
print("Error fetching cards for deck: ", err)
} else {
guard let cardSnapshot = cardSnapshot else { return }
cardsCount += cardSnapshot.count
}
group.leave()
})
})
}
group.leave()
}
group.notify(queue: .main) {
completion(cardsCount)
}
}
I try use nested DispatchGroup, there is my code :
class TranslateService {
private let myGroup = DispatchGroup()
func translateText(text:[String],closure:#escaping ((_ success:String?,_ error:Error?) -> Void)) {
var translateString: String = ""
var responseError: Error?
for index in 0...text.count - 1 {
let urlString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=trnsl.1.1.20171105T134956Z.795c7a0141d3061b.dc25bae76fa5740b2cdecb02396644dea58edd24&text=\(text[index])&lang=fa&format=plain&options=1"
if let allowString = Utilities.shareInstance.getQueryAllowedString(url: urlString) {
if let url = URL(string:allowString){
myGroup.enter()
Alamofire.request(url).responseJSON { response in
guard let responseData = response.data else {
self.myGroup.leave()
return
}
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
if let res = json as? [String:Any] {
if let code = res["code"] as? Int {
if code == 200 {
if let textArr = res["text"] as? [AnyObject] {
let flattArr = Utilities.shareInstance.flatStringMapArray(textArr)
if flattArr.count > 0 {
translateString += "،" + flattArr[0]
}
}
}
}
self.myGroup.leave()
}
}catch {
responseError = error
self.myGroup.leave()
}
}
self.myGroup.notify(queue: .main) {
print("Finished all requests.")
print(translateString)
closure(translateString, responseError)
}
}
}
}
}
}
class AddressService {
private let translateService: TranslateService = TranslateService()
private let myGroup = DispatchGroup()
func fetchAddressFromGeographicalLocation(latitude: Double, longitude: Double,closure:#escaping ((_ success:String,_ name:String,_ error:Error?) -> Void)) {
var address: String = ""
let name: String = ""
var responseError: Error?
if let url = URL(string:"https://maps.googleapis.com/maps/api/geocode/json?latlng=\(latitude),\(longitude)&key=AIzaSyAdEzHZfZWyjLMuuW92w5fkR86S3-opIF0&language=fa®ion=IR&locale=fa"){
self.myGroup.enter()
Alamofire.request(url).responseJSON { response in
guard let responseData = response.data else {
self.myGroup.leave()
return
}
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
if let addressDic = json as? [String:Any] {
if let result = addressDic["results"] as? [AnyObject] {
if result.count > 0 {
let flattRes = Utilities.shareInstance.flatMapArray(result)
let item = flattRes[0]
address = item["formatted_address"] as? String ?? ""
var res = address
if res.isContainEnglishCharachter {
self.myGroup.enter()
let resArr = res.components(separatedBy: ",")
var all : [String] = []
for item in resArr {
if item != " " {
all.append(item)
}}
self.translateService.translateText(text: all, closure: {stringAdd,error in
self.myGroup.enter()
if error != nil {
self.myGroup.leave()
}else {
address = stringAdd ?? ""
self.myGroup.leave()
} })
}else {
self.myGroup.leave()
}
}
}
}
}catch {
responseError = error
self.myGroup.leave()
}
self.myGroup.notify(queue: .main) {
// print("Finished all requests.")
closure(address, name, responseError)
}
}
}
}
}
All I want is that the myGroup I put in the class AddressService waiting for the myGroup that I put in the class TranslateService.
but now self.myGroup.notify not call in the AddressService class, So closure not work.
How can solve this problem, Thank you for all the answers.
I think you are over complicating it.
If I understood a bit, what you want to do is the following:
Get an address from the Address service.
Translate some words of that address, one by one, using the translation service.
When using the Address service there is only one call being done, so there is no need to use Dispatch Groups at this point. Dispatch Groups are used to synchronize more than one async call.
For your Translation service you can make good use of the Dispatch groups, since you are doing calls to the service inside a for loop. The problem here is, that the implementation is slightly wrong. You are setting the notification block inside the for loop, and it should be outside, so that it gets only triggered once, when all the calls inside the loop are done.
So move this block outside the for loop in the Translation service:
self.myGroup.notify(queue: .main) {
print("Finished all requests.")
print(translateString)
closure(translateString, responseError)
}
Now "Finished all requests." will only be printed once, when all requests are done.
In the address service you do not need dispatch groups at all. Just wait until the completion block is called.
self.translateService.translateText(text: all, closure: {stringAdd,error in
Everything is done here already.
}
Is it possible to fetch all child nodes from parent in firebase db?
Trying to say this:
I want to get all posts at the same time.
It fetches videos but only the current users videos. I want to fetch ALL users videos at the same time.
Here's some code to get more understanding of what I'm doing:
fileprivate func fetchAllPost() {
fetchPosts()
fetchAllPostsFromUserIds()
}
fileprivate func fetchAllPostsFromUserIds() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("posts").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
FIRDatabase.fetchUserWithUid(uid: key, completion: { (user) in
self.fetchPostsWithUser(user: user)
})
})
}) { (err) in
print("failed to fetch following users ids:", err)
}
}
var posts = [Post]()
fileprivate func fetchPosts() {
guard let currentUserID = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUid(uid: currentUserID) { (user) in
self.fetchPostsWithUser(user: user)
}
}
fileprivate func fetchPostsWithUser(user: User) {
let ref = FIRDatabase.database().reference().child("posts/\(user.uid)/")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
guard let dictionaries = snapshot.value as? [String: Any] else { return }
dictionaries.forEach({ (key,value) in
guard let dictionary = value as? [String: Any] else { return }
var post = Post(user: user, dictionary: dictionary)
post.id = key
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("likes").child(key).child(uid).observe(.value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
self.posts.append(post)
self.posts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
}, withCancel: { (err) in
print("Failed to fetch info for post")
})
print(self.posts)
})
}) { (error) in
print("Failed to fetch posts", error)
}
}
I don't know Swift, but you could fetch FIRDatabase.database().reference().child("posts") and then iterate over children.
We use Firebase as a backend for our service. It has a PeopleViewController that displays users within a radius of the current user. The radius can vary from 1 to 40 miles. Also there is a check for blocked users, age, sex. At the very beginning of the development of the project, a temporary implementation was made for this capability within the application. I created several managers who fulfilled these requests, and then gave the result to PeopleViewController. At the beginning of the project, because of the tight deadlines, pagination was not implemented, but the pagination was not particularly needed since the users even in the radius were less than 100. Now, in one radius there can be more than 600 users and I think it is not right that these requests are performed inside application. We move these functions from the app Side to the cloud function so that we can adjust how many users should be in each issue.
The problem is that even a warmed-up cloud function works slower than all functions related to this capability inside the application. On average, the code inside the application for a radius of 40 miles and the inclusion of all age groups returns 646 users in 1.5 seconds, while the best result is cloud functions 3.6. Below I will give examples of the time and code with the app side and cloud funciton.
The results are obtained using the code that is executed in the application:
1 number of users 646, 1.6775569915771484 seconds
2 number of users 646, 1.4022901058197021 seconds
3 number of users 646, 1.5957129001617432 seconds
The results with the cloud function time are specified in milliseconds. The parameters and location are the same as the code that is run from the application:
1 "timers": {
"afterVerify": 1,
"gotIdsFromGeofire": 1372,
"gotBlockedUsersIDs": 2463,
"gotBlockedByUsersIDs": 2467,
"gotUserSearchLocationModels": 5256,
"gotAllInitData": 5256,
"filtersAndSortsDone": 6168
},
2 "timers": {
"afterVerify": 6,
"gotIdsFromGeofire": 1209,
"gotBlockedUsersIDs": 1909,
"gotBlockedByUsersIDs": 1913,
"gotUserSearchLocationModels": 3800,
"gotAllInitData": 3800,
"filtersAndSortsDone": 4207
},
3 "timers": {
"afterVerify": 1,
"gotIdsFromGeofire": 812,
"gotBlockedUsersIDs": 1415,
"gotBlockedByUsersIDs": 1419,
"gotUserSearchLocationModels": 3912,
"gotAllInitData": 3913,
"filtersAndSortsDone": 4417
},
In the managers who request a list of users and filter the received data within the application.
extension PeopleListViewController {
#objc fileprivate func requestPeopleNearBy() {
let firPeopleSearchDatabaseManagerStart = Date().timeIntervalSince1970
firPeopleSearchDatabaseManager.searchPeople(success: { [weak self](userSearchLocationModels) in
let firPeopleSearchDatabaseManagerEnd = Date().timeIntervalSince1970
let resultTimeStamp = firPeopleSearchDatabaseManagerEnd - firPeopleSearchDatabaseManagerStart
debugPrint("firPeopleSearchDatabaseManager userSearchLocationModels", userSearchLocationModels.count, "resultTimeStamp", resultTimeStamp)
}) { (error) in
}
}
}
class FIRPeopleSearchDatabaseManager {
fileprivate enum MainGateways {
case userSearchLocationModel, userLocations
var description: String {
switch self {
case .userSearchLocationModel:
return "userSearchLocationModel"
case .userLocations:
return "userLocations"
}
}
}
func saveUserSearchLocationModel(_ userSearchLocationModel: UserSearchLocationModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let ref = Database.database().reference().child(MainGateways.userSearchLocationModel.description).child(userSearchLocationModel.userID)
let json = userSearchLocationModel.toJSON()
ref.updateChildValues(json, withCompletionBlock: { (error, ref) in
guard error == nil else {
fail?(error!)
return
}
success?()
})
}
}
private func downloadUserSearchLocationModel(_ userID: String, success: ((_ userSearchLocationModel: UserSearchLocationModel) -> Void)?, notExist: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let ref = Database.database().reference().child(MainGateways.userSearchLocationModel.description).child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull {
notExist?()
return
}
guard let json = snapshot.value as? [String : Any] else {
debugPrint("doest exist", userID)
notExist?()
return
}
guard let userSearchLocationModel = Mapper<UserSearchLocationModel>().map(JSON: json) else {
debugPrint("doest exist", userID)
notExist?()
return
}
guard !userSearchLocationModel.userID.isEmpty else {
notExist?()
return
}
success?(userSearchLocationModel)
}, withCancel: { (error) in
fail?(error)
})
}
}
func searchPeople(success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
guard let location = LocationManager.shared.currentLocation else { return }
let realmUserSettingsManager = RealmUserSettingsManager()
let miles = realmUserSettingsManager.getMaxDistanceInMiles() ?? 15
let distanceConverter = DistanceConvertor()
let radius = distanceConverter.convertedMilesToMetersForFIRRequest(miles)
DispatchQueue.global(qos: .background).async {
let usersGeofireRef = Database.database().reference().child(MainGateways.userLocations.description)
guard let geoFire = GeoFire(firebaseRef: usersGeofireRef) else { return }
guard let circleQuery = geoFire.query(at: location, withRadius: radius) else { return }
var usersListIDs = [String]()
var generalBlockModel = [BlockModel]()
let dispatchGroup = DispatchGroup()
let blockSystemManager = BlockSystemManager()
dispatchGroup.enter()
blockSystemManager.requestBlockingUserIDs(completion: { (blockModels, error) in
generalBlockModel = blockModels
dispatchGroup.leave()
})
dispatchGroup.enter()
circleQuery.observe(.keyEntered, with: { (key, location) in
if let _key = key {
if _key != currentUserID {
usersListIDs.append(_key)
}
}
})
circleQuery.observeReady({
circleQuery.removeAllObservers()
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .global(qos: .background), execute: {
if usersListIDs.count == 0 {
circleQuery.removeAllObservers()
success?([])
return
}
var unblockedUserListIDs = [String]()
for usersListID in usersListIDs {
let isContains = generalBlockModel.contains(where: { $0.userID == usersListID })
if !isContains {
unblockedUserListIDs.append(usersListID)
}
}
self.downloadUsers(usersListIDs: unblockedUserListIDs, success: success)
})
}
}
private func downloadUsers(usersListIDs: [String], success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?) {
var userIDsCount = usersListIDs.count
var userSearchLocationModels = [UserSearchLocationModel]()
for userID in usersListIDs {
self.downloadUserSearchLocationModel(userID, success: { (userSearchLocationModel) in
userSearchLocationModels.append(userSearchLocationModel)
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
}, notExist: {
userIDsCount -= 1
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
}, fail: { (error) in
userIDsCount -= 1
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
})
}
}
private func filter(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
filterByUserSearchFilter(userSearchLocationModels) { (searchFilteredUserSearchLocationModels) in
self.filterByOnlineAndRecentLaunchApp(searchFilteredUserSearchLocationModels, success: { (onlineRecentFilteredUserSearchLocationModels) in
success?(onlineRecentFilteredUserSearchLocationModels)
})
}
}
private func filterByUserSearchFilter(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
DispatchQueue.main.async {
let timeManager = TimeManager()
let realmUserSettingsManager = RealmUserSettingsManager()
guard let cuPeopleFilterSettings = realmUserSettingsManager.getUserPeopleFilterSettings() else { return }
var sortedUserSearchLocationModels = [UserSearchLocationModel]()
for userSearchLocationModel in userSearchLocationModels {
let userYearAge = timeManager.getUserAgeFromBirthdayTimeStamp(userSearchLocationModel.birthdayTimeStamp)
if cuPeopleFilterSettings.filterGenderModeIndex != 2 {
// sorting with mode
if cuPeopleFilterSettings.maxAgeValue != UserPeopleFilterSettings.Standard.maxAge {
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userYearAge <= cuPeopleFilterSettings.maxAgeValue && userSearchLocationModel.genderIndex == cuPeopleFilterSettings.filterGenderModeIndex {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
} else {
// max age settings
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userSearchLocationModel.genderIndex == cuPeopleFilterSettings.filterGenderModeIndex {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
}
} else {
if cuPeopleFilterSettings.maxAgeValue != UserPeopleFilterSettings.Standard.maxAge {
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userYearAge <= cuPeopleFilterSettings.maxAgeValue {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
} else {
// max age in settings
if userYearAge >= cuPeopleFilterSettings.minAgeValue {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
}
}
}
//sorting by online status and last seen timestamp
let onlineUsers = sortedUserSearchLocationModels.filter { $0.isOnline }
let otherUsersSortedByTimeStamp = sortedUserSearchLocationModels.filter { !$0.isOnline }.sorted { $0.lastSeenTimeStamp > $1.lastSeenTimeStamp }
var resultSortedArray = [UserSearchLocationModel]()
resultSortedArray.append(contentsOf: onlineUsers)
resultSortedArray.append(contentsOf: otherUsersSortedByTimeStamp)
success?(resultSortedArray)
}
}
/// 1. online users sorted by most recently active on the app 2. offline users sorted by most recently active on the app
private func filterByOnlineAndRecentLaunchApp(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let sortedUsers = userSearchLocationModels.sorted(by: { $0.recentActivityTimeStamp > $1.recentActivityTimeStamp })
success?(sortedUsers)
}
}
}
From BlockSystemManager
extension BlockSystemManager {
func requestBlockingUserIDs(completion: ((_ blockModels: [BlockModel], _ error: Error?) -> Void)?) {
DispatchQueue.main.async {
guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
DispatchQueue.global(qos: .background).async {
let dispatchGroup = DispatchGroup()
var generalBlockModels = [BlockModel]()
let blockedUsersRef = Database.database().reference().child(MainPath.blockingInfo.description).child(currentUserID).child(SubPath.blockedUsers.description)
dispatchGroup.enter()
blockedUsersRef.observeSingleEvent(of: .value, with: { (snap) in
if snap.value is NSNull {
dispatchGroup.leave()
return
}
guard let dict = snap.value as? [String : [String : Any]] else {
dispatchGroup.leave()
return
}
guard let blockModelsDict = Mapper<BlockModel>().mapDictionary(JSON: dict)?.values else {
dispatchGroup.leave()
return
}
let blockModels = Array(blockModelsDict)
generalBlockModels += blockModels
dispatchGroup.leave()
}, withCancel: { (error) in
dispatchGroup.leave()
})
let blockedByUsersRef = Database.database().reference().child(MainPath.blockingInfo.description).child(currentUserID).child(SubPath.blockedByUsers.description)
dispatchGroup.enter()
blockedByUsersRef.observeSingleEvent(of: .value, with: { (snap) in
if snap.value is NSNull {
dispatchGroup.leave()
return
}
guard let dict = snap.value as? [String : [String : Any]] else {
dispatchGroup.leave()
return
}
guard let blockModelsDict = Mapper<BlockModel>().mapDictionary(JSON: dict)?.values else {
dispatchGroup.leave()
return
}
let blockModels = Array(blockModelsDict)
generalBlockModels += blockModels
dispatchGroup.leave()
}, withCancel: { (error) in
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .global(qos: .background), execute: {
completion?(generalBlockModels, nil)
})
}
}
}
}
This function is located in the PeopleSearchManager and calls the cloud function
func searchPeopleFirstRequest(success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.main.async {
guard let location = LocationManager.shared.currentLocation else { return }
let realmUserSettingsManager = RealmUserSettingsManager()
let miles = realmUserSettingsManager.getMaxDistanceInMiles() ?? 15
let parameters = ["miles" : miles, "latitude" : location.coordinate.latitude, "longitude" : location.coordinate.longitude] as [String : Any]
DispatchQueue.global(qos: .background).async {
Auth.auth().currentUser?.getIDTokenForcingRefresh(true, completion: { (token, error) in
if let error = error {
// Handle error
fail?(error)
return
}
guard let _token = token else { return }
let headers = ["X-Auth-MyApp-Token" : _token]
guard let url = URL(string: BackendEndPoint.searchPeopleFirstRequest.path) else { return }
AlamofireMyAppManager.shared.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let value):
guard let dict = value as? [String : Any] else {
success?([])
return
}
guard let result = dict["result"] as? [[String : Any]] else {
success?([])
return
}
let userSearchLocationModels = Mapper<UserSearchLocationModel>().mapArray(JSONArray: result)
success?(userSearchLocationModels)
// for second request
if let lastUserSearchLocationModelID = dict["lastUserSearchLocationModelID"] as? String {
debugPrint("lastUserSearchLocationModelID", lastUserSearchLocationModelID)
self.peopleSecondRequestModel = PeopleSecondRequestModel(lastUserSearchLocationModelID: lastUserSearchLocationModelID, location: location, radius: miles, offset: userSearchLocationModels.count)
}
case .failure(let error):
debugPrint(error)
fail?(error)
}
})
})
}
}
}
Cloud function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const GeoFire = require('geofire');
const moment = require('moment');
const map = require('lodash').map;
const filter = require('lodash').filter;
const sortBy = require('lodash').sortBy;
const last = require('lodash').last;
module.exports = functions.https.onRequest((req, res) => {
const token = req.header('X-Auth-MyApp-Token');
const miles = req.query['miles'];
const latitude = req.query['latitude'];
const longitude = req.query['longitude'];
let currentUserID;
let outputUsers;
const startTime = moment();
const timers = {};
admin.auth().verifyIdToken(token)
.then((decodedToken) => {
timers.afterVerify = moment() - startTime;
currentUserID = decodedToken.uid;
console.log('userID', currentUserID);
const getUsersSearchModelsByLocationPromise = new Promise((resolve, reject) => {
try {
const userLocationRef = admin.database().ref().child('userLocations');
const geoFire = new GeoFire(userLocationRef);
const geoQuery = geoFire.query({
center: [Number(latitude), Number(longitude)],
radius: Number(miles) * 1.609
});
const usersIDs = [];
const keyListener = geoQuery.on('key_entered', (key) => usersIDs.push(key));
geoQuery.on('ready', () => {
keyListener.cancel();
geoQuery.cancel();
timers.gotIdsFromGeofire = moment() - startTime;
resolve(usersIDs)
});
} catch (error) {
reject(error)
}
})
.then((userIDs) => {
return Promise.all(map(userIDs, (id) => admin.database().ref().child('userSearchLocationModel').child(id)
.once('value')
.then((snapshot) => snapshot.val())
))
.then((users) => {
timers.gotUserSearchLocationModels = moment() - startTime;
return Promise.resolve(users);
})
});
const getBlockedUsersIDsPromise = Promise.all([
admin.database().ref().child('blockingInfo').child(currentUserID).child('blockedUsers')
.once('value')
.then((snapshot) => snapshot.val())
.then((users) => {
timers.gotBlockedUsersIDs = moment() - startTime;
return map(users, 'userID')
}),
admin.database().ref().child('blockingInfo').child(currentUserID).child('blockedByUsers')
.once('value')
.then((snapshot) => snapshot.val())
.then((users) => {
timers.gotBlockedByUsersIDs = moment() - startTime;
return map(users, 'userID')
}),
]);
const getFilterSettingsPromise = admin.database().ref().child('userPeopleFilterSettings').child(currentUserID)
.once('value')
.then((snapshot) => snapshot.val());
return Promise.all([
getUsersSearchModelsByLocationPromise,
getBlockedUsersIDsPromise,
getFilterSettingsPromise
]);
})
.then(([users, blockedUsersIds, filterSettings]) => {
timers.gotAllInitData = moment() - startTime;
outputUsers = users;
// filter nulls
outputUsers = filter(outputUsers, (user) => user);
// filter blocked
outputUsers = filter(outputUsers, (user) => blockedUsersIds.indexOf(user.userID) === -1);
// filter by gender
outputUsers = filterSettings.filterGenderModeIndex !== 2 ?
filter(outputUsers, (user) => user.genderIndex === filterSettings.filterGenderModeIndex) :
outputUsers;
// filter by age
outputUsers = filter(outputUsers, (user) => {
const userAge = moment().diff(user.birthdayTimeStamp * 1000, 'years');
if (filterSettings.maxAgeValue === 55 && userAge >= 55) return true;
return (userAge >= filterSettings.minAgeValue && userAge <= filterSettings.maxAgeValue);
});
// sort by recent activity timestamp
outputUsers = sortBy(outputUsers, 'recentActivityTimeStamp');
// slice
outputUsers = outputUsers.slice(0, 99);
timers.filtersAndSortsDone = moment() - startTime;
return res.status(200).send({
timers: timers,
result: outputUsers,
lastUserSearchLocationModelID: last(outputUsers)
});
})
.catch((error) => {
console.log('Error: ', error);
return res.status(500).send({
code: error.code,
message: error.message,
stack: error.stack
});
});
});
Also I want to say that I read this post, but as I wrote at the beginning, that even the heated function works like this. I also asked my question here. What is the reason for such a big difference in the result? How can this be remedied or what are the solutions to this problem?