0

Currently I am attempting to push values into an array of note objects from firebase The only issue is due to Firebases asynchronous nature, I am having trouble getting the main thread to wait until the fetch function is completed. I have viewed many answers on this site and I have read up on the Semaphores and Dispatch queue documentation however I cannot get this fetch to work. It appears that most of the people here are attempting to use a table view which I am not.

Here is the fetch code

func fetchUser(){


FIRDatabase.database().reference().child("notes").observe(.childAdded, with: { (snapshot) in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            let user = noteClass(dictionary: dictionary)
            self.coordinotes.append(user)
        }
    }, withCancel: nil)
} 

I have removed all of my semaphore and dispatch main attempts due to none of them working. This function is called in my view did load. When i check the values of my array that i push them into 'coordinotes' the value is not yet placed in and i get an out of bounds error.

Rest of code

import UIKit
import MapKit
import CoreLocation
import Firebase
import FirebaseDatabase

struct PreferencesKeys{
    static let savedItems = "savedItems"
}




class ViewController: UIViewController, CLLocationManagerDelegate{

    let manager = CLLocationManager()
    var coordinotes:[noteClass] = Array()
    var latitude = Double()
    var noteTime = noteBrain()

    //Map
    @IBOutlet weak var map: MKMapView!


    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
    {
        let location = locations[0]
        let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
        let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, noteTime.span)
        map.setRegion(region, animated: true)
        self.map.showsUserLocation = true
    }


    override func viewDidLoad()
    {
        super.viewDidLoad()
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))

        if FIRAuth.auth()?.currentUser?.uid == nil {
            perform(#selector(handleLogout), with: nil, afterDelay: 0)
        }
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization()
        manager.startUpdatingLocation()
        fetchUser()
        loadAllCoordinotes()
    }

    func handleLogout() {

        do {
            try FIRAuth.auth()?.signOut()
        } catch let logoutError {
            print(logoutError)
        }

        let loginController = LoginController()
        present(loginController, animated: true, completion: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


func loadAllCoordinotes() {
    let length = coordinotes.count - 1
    map.addAnnotation(coordinotes[length])
}
AL.
  • 36,815
  • 10
  • 142
  • 281
Kevin Maldjian
  • 147
  • 1
  • 10
  • Not sure I understood your question but have you tried a completion handler? – Mat Apr 18 '17 at 00:12
  • Yes, i however the implementations I tried always dealt with a link to a database and were not placing the created data into an array. Do you by any chance have any further documentation I can look at? – Kevin Maldjian Apr 18 '17 at 00:13
  • Making the UI wait for a long operation to end is not the best way to do it. You should have a delegate method invoked when the operation ends just to inform you. – José Fonte Apr 18 '17 at 00:14
  • @JoséFonte would this delegate return true once the thread is finished? Then the rest of the code can continue? – Kevin Maldjian Apr 18 '17 at 00:15
  • Where do you declare `coordinotes`? – Mat Apr 18 '17 at 00:17
  • I do not know firebase classes, but you have two ways of handling this, make the UI freeze but have a progress bar or spinner while the operation is not concluded or just let the UI run and have some listener/delegate method called when the operation ends. Just don't do it synchronously as your UI will be blocked until the long task runs. – José Fonte Apr 18 '17 at 00:18
  • @mat I have added the rest of the code – Kevin Maldjian Apr 18 '17 at 00:18
  • @JoséFonte is a delegate needed when it is not communicating between different views? – Kevin Maldjian Apr 18 '17 at 00:24
  • I posted an answer however, it's not clear if you declare `fetchUser` in the same class where you call it. Let me know if it works – Mat Apr 18 '17 at 00:27
  • Apple does use and abuse of the delegate pattern. You also have the [NotificationCenter](https://developer.apple.com/reference/foundation/notificationcenter) – José Fonte Apr 18 '17 at 00:28

1 Answers1

1
func fetchUser(_ completion:@escaping ([noteClass] , _ success: Bool)-> Void){
    let coordinotes = [noteClass]() 

FIRDatabase.database().reference().child("notes").observe(.childAdded, with: { (snapshot) in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            let user = noteClass(dictionary: dictionary)
            coordinotes.append(user)
        }
        completion(coordinotes, true)
    }, withCancel: nil)

}

and then you call it in viewDidLoad like this:

  fetchUser { (coordinotes, success) in
            if success {
                self.coordinotes = coordinote
                self.loadAllCoordinotes()
            }
        }
Mat
  • 6,236
  • 9
  • 42
  • 55
  • Thankyou for posting this. The first line is giving me an 'expected declaration' and is is telling me that "consecutive declarations must be separated by a ';'" I will try to play around with it. – Kevin Maldjian Apr 18 '17 at 00:28
  • try now. there was a typo, an extra `)` at the end of the signature – Mat Apr 18 '17 at 00:30
  • The code compiled, however the value of "found" in loadAllCoordinotes is still not being printed which means it is still empty and the map is not displaying the pins. Also should it be self.coordinotes.append(user) on your line 6. – Kevin Maldjian Apr 18 '17 at 00:34
  • Through using a print statement during "if success" i have found that that if never executes. I will keep looking at it. – Kevin Maldjian Apr 18 '17 at 00:36
  • in the completion handler you return the array declared in line 2. Then in viewDidLoad you assign all the value to self. Have you tried to add a breakpoint to see if you are appending anything ? – Mat Apr 18 '17 at 00:37
  • I will test now. – Kevin Maldjian Apr 18 '17 at 00:42
  • From my test it appears that nothing is being added to the coordinotes array. Additonally if success never happens but the append to the function in the fetch is being appended. – Kevin Maldjian Apr 18 '17 at 00:45
  • Are you testing this line 'coordinotes.append(user)' ? – Mat Apr 18 '17 at 00:51
  • Is fetchUser declared in a separate class? Are you returning on completion the array declared in line 2 or self.coordinotes? – Mat Apr 18 '17 at 00:52
  • Fetch user is only in this view controller. I have tested coordinotes.append(user) and the user is being appended to that array inside the class however, the coordinotes array in the whole project is not being filled due to the if statement in the view did load never executing. – Kevin Maldjian Apr 18 '17 at 00:56
  • Can you try the updated code? I have never used firebase :) I moved the completion after your if statement – Mat Apr 18 '17 at 00:59
  • Ok now coordinotes in the if statement gets each of the appended values, but through using breakpoints i can see that loadAllCoordinotes still gets called First causing the for x in coordinotes to not execute. – Kevin Maldjian Apr 18 '17 at 01:08
  • Don't call loadAllCootdinotes() in viewDidLoad but move it inside the completion.i will update the answer – Mat Apr 18 '17 at 01:14
  • I have also updated my loadAllcoordinates function to prevent the same annotation added multiple times. For some reason the pins are not showing up on the map but i believe we have solved the async problem! – Kevin Maldjian Apr 18 '17 at 01:23
  • I will look at the rest of the code later. Where do you add the coordinates of the annotation ? something like `let annotation = MKPointAnnotation() annotation.coordinate = CLLocationCoordinate2DMake(your latitude from coordinotes, your long from coordinotes)` mapView.addAnnotation(annotation)` – Mat Apr 18 '17 at 01:39
  • is there a latitude and longitude property in the `noteClass`? – Mat Apr 18 '17 at 01:42
  • I have fixed it! had a capitalization in my dictionary and the parameters were not matching. It works now. Thankyou so much. You have saved me countless hours. – Kevin Maldjian Apr 18 '17 at 01:43